diff --git a/docs/src/content/docs/development/Architecture/model-manager.mdx b/docs/src/content/docs/development/Architecture/model-manager.mdx index dd3a5680b95..d3f56039373 100644 --- a/docs/src/content/docs/development/Architecture/model-manager.mdx +++ b/docs/src/content/docs/development/Architecture/model-manager.mdx @@ -1015,10 +1015,19 @@ An estimator that declares an `EstimatorSpec` returns a `WorkingMemoryEstimate` To write such an estimator (`invokeai/backend/util/vae_working_memory.py` has five): - Register an `EstimatorSpec` at module import, and list the module in `ESTIMATOR_MODULES` (`working_memory/spec.py`); the data test fails on a module that declares a spec without being listed. -- Split the estimator in two. The impure part reads the model and the device and records every runtime decision (tile, element size, score-matrix bytes) as a feature. The pure `terms(features, values)` turns features and coefficient values into terms, building fitted terms with `WorkingMemoryTerm.fitted(...)`. The public function returns `SPEC.estimate(features, device)`. +- Split the estimator in two. The impure part reads the model and the device and records every runtime decision (tile, the dtype the operation runs in, score-matrix bytes) as a feature, and refuses a dtype the spec has no line for with a `ValueError` naming the estimator, dtype and platform. The pure `terms(features, values)` turns features and coefficient values into terms, building fitted terms with `WorkingMemoryTerm.fitted(...)`. The public function returns `SPEC.estimate(features, device)`. +- Record the dtype the operation will run in, not the one the cached model holds. The SD1/SDXL nodes cast the shared VAE with `sd_vae_run_dtype`: fp16 for every GPU run that does not ask for fp32 (bf16 never runs), fp32 when asked and for a CPU decode. Their estimate prices the same function's answer, plus what the in-place cast adds to the resident weights. Uncast VAEs compute in their first floating-point parameter wider than a byte, so fp8 or packed SDNQ storage does not count. - Bump `version` whenever `terms` changes the formula. -- Ship one coefficient entry per measured platform value in `invokeai/backend/working_memory/data/`, either with its own `values` or with `values_from` another estimator's entry. Unmeasured platform values (MPS, XPU, CPU) are priced as `UNMEASURED_PRICED_AS` in `working_memory/platform.py` says. -- Ship the measured points next to them: peak reserved bytes (`reserved_peak`), one fresh process per point. `tests/backend/working_memory/` holds each estimate to at least its point, and where a fitted term decides it, to at most 1.3 times the point, or the point's tighter `max_ratio`. The `device_working_mem_gb` floor counts only where an analytic term decides. Every value an entry carries must be decided by at least one point. +- Ship one coefficient entry per measured platform in `invokeai/backend/working_memory/data/`, either with its own `values` or with `values_from` another estimator's entry, which may carry more values than the borrower reads. + - A platform is the convolution backend (`cudnn`, `miopen`, `mps`, `xpu`, `cpu`), torch's caching allocator and the OS. The allocator is torch's backend first (`cudaMallocAsync` ignores expandable segments), then `expandable` if the allocator environment variables enable expandable segments on the native allocator, else `native`; anything but a CUDA/HIP GPU is `native`. + - `MEASURED` in `working_memory/platform.py` lists the measured backend, allocator and OS combinations (an OS of `None` for every OS). `priced_as` prices every other pair as one of them, keeping the OS: a backend nothing is measured on as cuDNN with the native allocator, an allocator not measured with its backend as that backend's native allocator. There the entry that carries the values raises them to its `unmeasured_floor`, the pre-dtype slopes. + - An entry applies to the OS only when its platform names one, and then takes precedence there over the one that does not. Only MIOpen names one. With the native allocator, Windows demand is concave and up to 1.6x Linux's, so Windows has its own fit and every other OS keeps the Linux one. Expandable segments are measured on Windows only, where they are Invoke's ROCm default, so `MEASURED` lists them for Windows and every other OS prices them as the native allocator there. cuDNN is priced the same on every OS. +- Ship the measured points next to them, one fresh process per point: `reserved_peak` (the allocator's peak reserved bytes), `driver_peak` (the driver-level device usage), or `demand_peak` (the largest of driver-level, reserved and allocated peak, over two runs, which catches the memory HIP maps beyond the allocator on a process's first operation). A point names the OS only where it measured an OS-specific entry. `tests/backend/working_memory/` holds each estimate to at least its point, and where a fitted term decides an in-fit point, to at most 1.3 times the point, or the point's tighter `max_ratio`. Points no line can hold within 1.3x are listed with their own ratio in the test's `UPPER_BOUND_EXCEPTIONS`. The `device_working_mem_gb` floor counts only where an analytic term decides. +- Fit the values, never hand-tune them. The AutoencoderKL estimators price their convolutions as a line per operation and dtype: `k__` bytes per output pixel plus a base of `b__` bytes. + - `working_memory/fit.py` fits each line on the untiled single-sample in-fit points of every estimator that resolves to the entry. The line is the edge of the points' upper convex hull (with the origin, so the base is never negative) that over-reserves its worst point least, the steeper on a tie and never a falling one. It needs at least two sizes twice apart in pixels. The shipped values add a 5% margin and round up: the slope to a whole byte per pixel, the base to a whole MiB. + - A point measured in a regime its platform does not run sets `in_fit: false`: it fits nothing and gets only the lower bound (CUDA with the math attention kernel forced). The test lists every such point. + - A line no in-fit point measures is a declared stand-in, listed in the entry's `stand_ins` and never below the pre-dtype slope. MIOpen with the native allocator off Windows prices fp16 and fp32 at the pre-dtype figure, and CogView4's lines there at its expandable-segments ones. + - Every value an entry carries is a stand-in or decided by an in-fit point. The data test fails when a fitted value is not the fit of its points, naming the value to ship, and when an in-fit point measures a stand-in's line. ### Emitting model loading events diff --git a/invokeai/app/invocations/vae/image_to_latents.py b/invokeai/app/invocations/vae/image_to_latents.py index d599fadd862..1be14f136b2 100644 --- a/invokeai/app/invocations/vae/image_to_latents.py +++ b/invokeai/app/invocations/vae/image_to_latents.py @@ -27,7 +27,7 @@ from invokeai.backend.stable_diffusion.diffusers_pipeline import image_resized_to_grid_as_tensor from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params from invokeai.backend.util.devices import TorchDevice -from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl +from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl, sd_vae_run_dtype """ SDXL VAE color compensation values determined experimentally to reduce color drift. @@ -86,9 +86,10 @@ def vae_encode( with vae_info.model_on_device(working_memory=estimated_working_memory) as (_, vae): assert isinstance(vae, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL" orig_dtype = vae.dtype - if upcast: - vae.to(dtype=torch.float32) - + # The dtype the estimate above priced: fp32 when upcasting, fp16 otherwise, on every device. + run_dtype = sd_vae_run_dtype("encode", upcast, vae_info.compute_device) + vae.to(dtype=run_dtype) + if run_dtype == torch.float32: use_torch_2_0_or_xformers = hasattr(vae.decoder, "mid_block") and isinstance( vae.decoder.mid_block.attentions[0].processor, ( @@ -107,10 +108,6 @@ def vae_encode( # else: # latents = latents.float() - else: - vae.to(dtype=torch.float16) - # latents = latents.half() - if tiled: vae.enable_tiling() else: diff --git a/invokeai/app/invocations/vae/latents_to_image.py b/invokeai/app/invocations/vae/latents_to_image.py index 29c6a1b9db8..bb2aafd0bfb 100644 --- a/invokeai/app/invocations/vae/latents_to_image.py +++ b/invokeai/app/invocations/vae/latents_to_image.py @@ -20,7 +20,7 @@ from invokeai.backend.stable_diffusion.extensions.seamless import SeamlessExt from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params from invokeai.backend.util.devices import TorchDevice -from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl +from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl, sd_vae_run_dtype @invocation( @@ -74,16 +74,12 @@ def invoke(self, context: InvocationContext) -> ImageOutput: # wrongly place the latents (and thus the whole decode) on the CPU (see #9373). device = vae_info.compute_device latents = latents.to(device) - # Force fp32 when running on CPU (e.g. when the VAE is configured cpu_only). fp16 conv does run on CPU with + # fp32 when asked, and on the CPU (e.g. when the VAE is configured cpu_only): fp16 conv does run on CPU with # the pinned torch, but it's much slower than fp32 there, and SD/SDXL VAE has known fp16 overflow issues - # that produce black images — fp32 avoids both. - if self.fp32 or device.type == "cpu": - # FP32 mode: convert everything to float32 for maximum precision - vae.to(dtype=torch.float32) - latents = latents.float() - else: - vae.to(dtype=torch.float16) - latents = latents.half() + # that produce black images — fp32 avoids both. fp16 otherwise. The estimate above priced the same rule. + run_dtype = sd_vae_run_dtype("decode", self.fp32, device) + vae.to(dtype=run_dtype) + latents = latents.to(dtype=run_dtype) if use_tiling: vae.enable_tiling() diff --git a/invokeai/app/util/torch_cuda_allocator.py b/invokeai/app/util/torch_cuda_allocator.py index 7e94be57900..280f5bbe752 100644 --- a/invokeai/app/util/torch_cuda_allocator.py +++ b/invokeai/app/util/torch_cuda_allocator.py @@ -6,13 +6,26 @@ import sys from pathlib import Path -# Every variable torch reads its caching-allocator configuration from. `ModelCache._expandable_segments_enabled` keeps -# its own copy of this list: the model cache imports torch, and this module must not. +# Every variable torch reads its caching-allocator configuration from. _ALLOCATOR_CONF_ENV_VARS = ("PYTORCH_ALLOC_CONF", "PYTORCH_CUDA_ALLOC_CONF", "PYTORCH_HIP_ALLOC_CONF") ROCM_WINDOWS_ALLOC_CONF = "expandable_segments:True" +def expandable_segments_enabled() -> bool: + """Whether the torch caching allocator runs in expandable-segments mode. + + The mode is configured through the allocator env vars before torch import (InvokeAI's own + `pytorch_cuda_alloc_conf` setting is plumbed into PYTORCH_CUDA_ALLOC_CONF the same way), so + parsing them is authoritative for the life of the process. It lives here, beside the variables, because this + module imports neither torch nor its callers: the model cache and the working-memory platform. + """ + for var in _ALLOCATOR_CONF_ENV_VARS: + if "expandable_segments:true" in os.environ.get(var, "").replace(" ", "").lower(): + return True + return False + + def _installed_torch_version() -> str | None: """The installed torch distribution's version, read from its metadata so torch itself is not imported.""" try: diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache.py b/invokeai/backend/model_manager/load/model_cache/model_cache.py index 3691f374425..6cf1345568e 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache.py @@ -1,6 +1,5 @@ import gc import logging -import os import queue import sys import threading @@ -15,6 +14,7 @@ import psutil import torch +from invokeai.app.util.torch_cuda_allocator import expandable_segments_enabled from invokeai.backend.model_manager.load.memory_snapshot import MemorySnapshot from invokeai.backend.model_manager.load.model_cache.cache_record import CacheRecord from invokeai.backend.model_manager.load.model_cache.cache_stats import CacheStats @@ -39,20 +39,6 @@ from invokeai.backend.util.logging import InvokeAILogger from invokeai.backend.util.prefix_logger_adapter import PrefixedLoggerAdapter - -def _expandable_segments_enabled() -> bool: - """Whether the torch caching allocator runs in expandable-segments mode. - - The mode is configured through the allocator env vars before torch import (InvokeAI's own - `pytorch_cuda_alloc_conf` setting is plumbed into PYTORCH_CUDA_ALLOC_CONF the same way), so - parsing them is authoritative for the life of the process. - """ - for var in ("PYTORCH_ALLOC_CONF", "PYTORCH_CUDA_ALLOC_CONF", "PYTORCH_HIP_ALLOC_CONF"): - if "expandable_segments:true" in os.environ.get(var, "").replace(" ", "").lower(): - return True - return False - - # Size of a GB in bytes. GB = 2**30 @@ -2231,7 +2217,7 @@ def _release_allocator_blocks_for_reserve(self, working_mem_bytes: Optional[int] driver_free, _ = TorchDevice.cuda_mem_get_info(self._execution_device) if driver_free >= reserve: return - if _expandable_segments_enabled(): + if expandable_segments_enabled(): # Freed pages inside a segment are not counted as reclaimable there, but empty_cache() unmaps them. releasable = torch.cuda.memory_reserved(self._execution_device) - torch.cuda.memory_allocated( self._execution_device @@ -2403,7 +2389,7 @@ def _get_reclaimable_allocator_bytes(self) -> int: `_offload_unlocked_models` runs one after each offload in that mode; what it cannot do is make the credit trustworthy before it runs. """ - if _expandable_segments_enabled(): + if expandable_segments_enabled(): return 0 try: if self._execution_device.type == "cuda": @@ -2618,7 +2604,7 @@ def _offload_unlocked_models( # them -- so it is deliberately loop-local: it stops this loop offloading everything, and # the load that follows re-measures for itself (`lock`, `make_room_in_vram`) and streams # from RAM if the room did not materialize. - empty_cache_per_offload = _expandable_segments_enabled() + empty_cache_per_offload = expandable_segments_enabled() vram_bytes_freed_uncounted = 0 # TODO(ryand): Give more thought to the offloading policy used here. cache_entries_increasing_size = sorted(self._cached_models.values(), key=lambda x: x.cached_model.total_bytes()) diff --git a/invokeai/backend/util/vae_working_memory.py b/invokeai/backend/util/vae_working_memory.py index bc2f3123d64..91f2acd4318 100644 --- a/invokeai/backend/util/vae_working_memory.py +++ b/invokeai/backend/util/vae_working_memory.py @@ -16,6 +16,7 @@ from invokeai.backend.util.logging import InvokeAILogger from invokeai.backend.util.vae_tiling_scope import resolve_tile_size from invokeai.backend.working_memory.estimate import FeatureValue, WorkingMemoryEstimate, WorkingMemoryTerm +from invokeai.backend.working_memory.platform import platform_for from invokeai.backend.working_memory.spec import EstimatorSpec, register # Share of the device's memory an untiled decode may claim before the decode nodes tile it up front (see @@ -77,7 +78,7 @@ def should_pretile_vae_decode( return True -# The diffusers AutoencoderKL (SD1/SDXL, SD3, CogView4) and the FLUX.1 AutoEncoder run the same +# The diffusers AutoencoderKL (SD1/SDXL, SD3) and the FLUX.1 AutoEncoder run the same # mid-block self-attention as the FLUX.2 VAE: one head over the 512-channel width, on the # 8x-downsampled grid. Where that attention runs on the math kernel (see `sdpa_score_matrix_bytes`) # its score matrix is the dominant term for a large untiled image, exactly as it is for FLUX.2; @@ -114,25 +115,85 @@ def _vae_mid_block_score_matrix_bytes( ) -# The SD1/SDXL, SD3, CogView4, FLUX.1 and FLUX.2 VAEs run the same diffusers AutoencoderKL convolution stack, and pay -# the same for it: peak reserved bytes per output pixel per element byte, one fitted coefficient per operation -# (`k_decode`, `k_encode`) and convolution backend. The values ship in `backend/working_memory/data/vae.json`, with the -# points they were fitted on and the per-card table behind them; `scripts/calibrate_flux2_working_memory.py --only vae` -# reproduces that table on any build. +# The SD1/SDXL, SD3, FLUX.1 and FLUX.2 VAEs run the same diffusers AutoencoderKL convolution stack, and pay the same +# for it: a line in the output's pixel count per operation and the dtype it runs in, `k__` +# bytes per pixel plus `b__` bytes the process pays whatever the size, shipped per convolution +# backend and allocator (`platform_for`). The values ship in `backend/working_memory/data/vae.json` with the points +# they are fitted on (`backend/working_memory/fit.py`); `scripts/calibrate_flux2_working_memory.py --only vae` +# measures a build against them. # -# MIOpen's convolution workspaces are simply larger than cuDNN's -- ~1.6x for a decode, ~2.4x for an encode. This is -# not the attention term: it shows up identically on the fused path, and the implied coefficients are flat across -# resolution on both backends, so the linear model itself holds. Nor is "encoding costs half of decoding" architectural: -# it is a cuDNN property (0.49 there, 0.78 on MIOpen), which is why each operation carries its own coefficient instead -# of a ratio. Shipping the MIOpen values everywhere would add ~60% to every cuDNN decode for nothing, so the -# coefficients follow the backend of the device the VAE runs on (`platform_for`). +# None of the three scales out of the line. The demand does not follow the element size: on MIOpen fp16 needs about a +# third of what bf16 does, and on both backends fp32 needs less per value byte than bf16. The allocator matters as +# much: `cudaMallocAsync` needs 25-35% less than the native allocator on cuDNN. And MIOpen's bf16 workspaces are +# larger than cuDNN's, ~1.5x for a decode and ~2.4x for an encode -- not the attention term, which shows up the same +# on the fused path. The base is why the per-pixel figure rises at small sizes; on HIP it includes the ~300 MiB the +# driver maps beyond the allocator's own figure on the first operation of a process. +# +# Each estimator declares the lines of the dtypes it can run, and the estimate prices the dtype the operation will run +# in, which is not always the dtype the cached VAE holds (see `sd_vae_run_dtype`). CogView4's VAE is an AutoencoderKL +# too, but a wider one (block widths up to 1024, three layers per block, no mid-block attention): it carries its own +# lines, about 1.9x the family's for a decode. # # Caveat for AMD users: `MIOPEN_FIND_MODE=2` (FAST) selects convolution algorithms heuristically instead of by # benchmark, and measured a uniform 1.28x more memory at every resolution when the MIOpen values were first fitted. -# Invoke has since made FAST its default (`run_app.py`); the RX 9060 XT measurement was taken under it and did not show -# the 1.28x. If you see decodes exceed their reservation on another card, raise `device_working_mem_gb`. -_AUTOENCODER_KL_COEFFICIENTS = frozenset({"k_decode", "k_encode"}) -_CONV_BACKEND_KEYS = ("conv_backend",) +# Invoke has since made FAST its default (`run_app.py`); the RX 9060 XT points were taken under it. If you see decodes +# exceed their reservation on another card, raise `device_working_mem_gb`. +_PLATFORM_KEYS = ("conv_backend", "allocator") +_DTYPE_NAMES: dict[torch.dtype, str] = {torch.float16: "fp16", torch.bfloat16: "bf16", torch.float32: "fp32"} +_ELEMENT_SIZES = {"fp16": 2, "bf16": 2, "fp32": 4} + + +def _convolution_coefficients(*dtypes: str) -> frozenset[str]: + """The coefficients of the convolution lines for both operations in these dtypes.""" + return frozenset( + f"{part}_{operation}_{dtype}" for part in ("k", "b") for operation in ("decode", "encode") for dtype in dtypes + ) + + +def _dtype_name(dtype: torch.dtype) -> str: + name = _DTYPE_NAMES.get(dtype) + if name is None: + raise ValueError(f"No VAE working-memory coefficients are measured for {dtype}.") + return name + + +def _compute_dtype(vae: torch.nn.Module) -> torch.dtype: + """The dtype a VAE no node casts computes in: that of its first floating-point parameter wider than a byte. + + Narrower or integer weights (fp8 storage, an SDNQ VAE's packed ones) are what a layer dequantizes from, not what + its activations are. + """ + for param in vae.parameters(): + if param.is_floating_point() and param.element_size() > 1: + return param.dtype + raise ValueError(f"{type(vae).__name__} has no floating-point parameters to compute in.") + + +def _estimate(spec: EstimatorSpec, features: Mapping[str, FeatureValue], device: torch.device) -> WorkingMemoryEstimate: + """`spec.estimate`, refusing a dtype the spec has no line for with the estimator, dtype and platform named, rather + than a bare `KeyError` from inside the terms.""" + line = f"k_{features['operation']}_{features['dtype']}" + if line not in spec.coefficients: + raise ValueError( + f"Working-memory estimator '{spec.id}' has no {features['dtype']} {features['operation']} line on " + f"{platform_for(device)}: the VAE computes in a dtype it is not measured for." + ) + return spec.estimate(features, device) + + +def _convolutions( + features: Mapping[str, FeatureValue], values: Mapping[str, float], pixels: float, samples: int = 1 +) -> tuple[WorkingMemoryTerm, ...]: + """The convolution line of this operation and dtype over `pixels` output pixels, for `samples` samples. + + The base scales with the samples too. That is an upper bound rather than a fit -- the base is paid once per process + -- and it keeps a batch at exactly that many single-sample estimates. + """ + line = f"{features['operation']}_{features['dtype']}" + return ( + WorkingMemoryTerm.fitted("convolutions", f"k_{line}", pixels * samples, values), + WorkingMemoryTerm.fitted("convolution_base", f"b_{line}", samples, values), + ) def _peak_of_two_phases( @@ -170,40 +231,60 @@ def _peak_of_two_phases( def _sd_terms(features: Mapping[str, FeatureValue], values: Mapping[str, float]) -> tuple[WorkingMemoryTerm, ...]: - coefficient = f"k_{features['operation']}" - element_size = cast(int, features["element_size"]) tile = cast(int | None, features["tile"]) if tile is not None: - area_bytes = tile * tile * element_size # We add 25% to the working memory estimate when tiling is enabled to account for factors like tile overlap # and number of tiles. We could make this more precise in the future, but this should be good enough for # most use cases. - convolutions = WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes * 1.25, values) + terms = _convolutions(features, values, tile * tile * 1.25) else: - area_bytes = cast(int, features["height"]) * cast(int, features["width"]) * element_size - convolutions = WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes, values) + terms = _convolutions(features, values, cast(int, features["height"]) * cast(int, features["width"])) - terms: tuple[WorkingMemoryTerm, ...] = (convolutions,) # The tiny autoencoder has no mid-block attention, so it is priced without a score matrix. if features["vae_class"] == "AutoencoderKL": terms = _peak_of_two_phases(terms, cast(int, features["score_matrix_bytes"])) - if features["fp32"]: - # If we are running in FP32, then we should account for the likely increase in model size (~250MB). - terms += (WorkingMemoryTerm("fp32_weights", 250 * 2**20, True),) + upcast_weight_bytes = cast(int, features["upcast_weight_bytes"]) + if upcast_weight_bytes: + terms += (WorkingMemoryTerm("upcast_weights", upcast_weight_bytes, True),) return terms VAE_SD_SPEC = register( EstimatorSpec( id="vae.sd", - version=1, - coefficients=_AUTOENCODER_KL_COEFFICIENTS, - platform_keys=_CONV_BACKEND_KEYS, + version=2, + coefficients=_convolution_coefficients("fp16", "fp32"), + platform_keys=_PLATFORM_KEYS, terms=_sd_terms, ) ) +def sd_vae_run_dtype(operation: Literal["encode", "decode"], fp32: bool, device: torch.device) -> torch.dtype: + """The dtype the SD1/SDXL nodes cast the shared VAE to before they run it: `l2i` and `i2l` cast with this, and + `estimate_vae_working_memory_sd15_sdxl` prices it, so the two cannot drift. + + fp32 when the node asks for it, and for a decode on the CPU; fp16 otherwise, on every GPU and for every + `precision`, so bf16 never runs. The cached VAE's own dtype says nothing about it: it is whatever the previous run + cast it to. + """ + if fp32 or (operation == "decode" and device.type == "cpu"): + return torch.float32 + return torch.float16 + + +def _upcast_weight_bytes(vae: torch.nn.Module, run_dtype: torch.dtype) -> int: + """What the node's in-place cast to `run_dtype` adds to the VAE's resident weights. + + The cast runs once the cache has placed the VAE, so the growth comes out of the reservation: 160 MiB for the 83.7M + parameters of the SD1/SDXL VAE going from fp16 to fp32, nothing for a VAE a previous run left in fp32. Measured + points exclude resident weights by construction (they record 0 here), so the fitted line does not cover it. An + upcast encode casts the decoder's mid-block and post-quant conv back, so for it this counts a little more than + grows: conservative. + """ + return sum(p.numel() * max(0, run_dtype.itemsize - p.element_size()) for p in vae.parameters()) + + def estimate_vae_working_memory_sd15_sdxl( operation: Literal["encode", "decode"], image_tensor: torch.Tensor, @@ -214,12 +295,13 @@ def estimate_vae_working_memory_sd15_sdxl( ) -> WorkingMemoryEstimate: """Estimate the working memory required to encode or decode the given tensor. - It was found experimentally that the peak working memory scales linearly with the number of pixels and the element - size (precision). This estimate is accurate for both SD1 and SDXL. `device` is where the VAE will run; it picks the - convolution backend's coefficients and defaults to the session device. + It was found experimentally that the peak working memory is a line in the number of pixels, per dtype. This + estimate is accurate for both SD1 and SDXL. It prices the dtype the node will cast the VAE to (`sd_vae_run_dtype`), + not the one the cached VAE holds. `device` is where the VAE will run; it picks the platform's coefficients and + defaults to the session device. """ - element_size = 4 if fp32 else 2 device = device if device is not None else TorchDevice.choose_torch_device() + run_dtype = sd_vae_run_dtype(operation, fp32, device) latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1 height = latent_scale_factor_for_operation * image_tensor.shape[-2] width = latent_scale_factor_for_operation * image_tensor.shape[-1] @@ -233,26 +315,23 @@ def estimate_vae_working_memory_sd15_sdxl( "height": height, "width": width, "tile": tile_size, - "element_size": element_size, - "fp32": fp32, + "dtype": _dtype_name(run_dtype), "vae_class": "AutoencoderKL" if isinstance(vae, AutoencoderKL) else "AutoencoderTiny", + "upcast_weight_bytes": _upcast_weight_bytes(vae, run_dtype), } if isinstance(vae, AutoencoderKL): - score_dtype = torch.float32 if fp32 else next(vae.parameters()).dtype score_h, score_w = (tile_size, tile_size) if tile_size is not None else (height, width) - features["score_matrix_bytes"] = _vae_mid_block_score_matrix_bytes(score_h, score_w, score_dtype, device=device) + features["score_matrix_bytes"] = _vae_mid_block_score_matrix_bytes(score_h, score_w, run_dtype, device=device) - return VAE_SD_SPEC.estimate(features, device) + return _estimate(VAE_SD_SPEC, features, device) def _single_pass_terms( features: Mapping[str, FeatureValue], values: Mapping[str, float] ) -> tuple[WorkingMemoryTerm, ...]: """An untiled AutoencoderKL pass: the convolutions over the whole image, or the mid-block score matrix.""" - coefficient = f"k_{features['operation']}" - area_bytes = cast(int, features["height"]) * cast(int, features["width"]) * cast(int, features["element_size"]) - convolutions = WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes, values) - return _peak_of_two_phases((convolutions,), cast(int, features["score_matrix_bytes"])) + convolutions = _convolutions(features, values, cast(int, features["height"]) * cast(int, features["width"])) + return _peak_of_two_phases(convolutions, cast(int, features["score_matrix_bytes"])) def _single_pass_features( @@ -261,23 +340,30 @@ def _single_pass_features( latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1 height = latent_scale_factor_for_operation * image_tensor.shape[-2] width = latent_scale_factor_for_operation * image_tensor.shape[-1] - param = next(vae.parameters()) + dtype = _compute_dtype(vae) return { "operation": operation, "height": height, "width": width, - "element_size": param.element_size(), - "score_matrix_bytes": _vae_mid_block_score_matrix_bytes(height, width, param.dtype, device=device), + "dtype": _dtype_name(dtype), + "score_matrix_bytes": _vae_mid_block_score_matrix_bytes(height, width, dtype, device=device), } +def _cogview4_terms(features: Mapping[str, FeatureValue], values: Mapping[str, float]) -> tuple[WorkingMemoryTerm, ...]: + """An untiled CogView4 VAE pass: its convolutions over the whole image. It has no mid-block attention + (`mid_block_add_attention: false`), so there is no score matrix to price.""" + return _convolutions(features, values, cast(int, features["height"]) * cast(int, features["width"])) + + +# The CogView4 loader forces bf16: the VAE produces black images in fp16. VAE_COGVIEW4_SPEC = register( EstimatorSpec( id="vae.cogview4", - version=1, - coefficients=_AUTOENCODER_KL_COEFFICIENTS, - platform_keys=_CONV_BACKEND_KEYS, - terms=_single_pass_terms, + version=2, + coefficients=_convolution_coefficients("bf16"), + platform_keys=_PLATFORM_KEYS, + terms=_cogview4_terms, ) ) @@ -294,7 +380,14 @@ def estimate_vae_working_memory_cogview4( device. """ device = device if device is not None else TorchDevice.choose_torch_device() - return VAE_COGVIEW4_SPEC.estimate(_single_pass_features(operation, image_tensor, vae, device), device) + latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1 + features: dict[str, FeatureValue] = { + "operation": operation, + "height": latent_scale_factor_for_operation * image_tensor.shape[-2], + "width": latent_scale_factor_for_operation * image_tensor.shape[-1], + "dtype": _dtype_name(_compute_dtype(vae)), + } + return _estimate(VAE_COGVIEW4_SPEC, features, device) # What a tiled decode does *not* bound: the assembled image, several times over, per output pixel. @@ -330,28 +423,23 @@ def estimate_vae_working_memory_cogview4( def _flux_terms(features: Mapping[str, FeatureValue], values: Mapping[str, float]) -> tuple[WorkingMemoryTerm, ...]: operation = features["operation"] - coefficient = f"k_{operation}" out_h = cast(int, features["height"]) out_w = cast(int, features["width"]) - element_size = cast(int, features["element_size"]) + element_size = _ELEMENT_SIZES[cast(str, features["dtype"])] tile = cast(int | None, features["tile"]) if tile is None: - area_bytes = out_h * out_w * element_size - convolution_phase: tuple[WorkingMemoryTerm, ...] = ( - WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes, values), - ) + convolution_phase = _convolutions(features, values, out_h * out_w) return _peak_of_two_phases(convolution_phase, cast(int, features["score_matrix_bytes"])) # `calc_tiles_min_overlap` clamps the tile to the image *per axis*, so the largest tile the # decode builds is this, not `tile` squared. Without the clamp an 8192px tile on a # 1024x8200 image -- tiled, because one axis exceeds it -- prices 8192x8192. - area_bytes = min(tile, out_h) * min(tile, out_w) * element_size # A 25% margin for tile overlap and the number of tiles, mirroring the SD1/SDXL estimator. - convolutions = WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes * 1.25, values) + convolutions = _convolutions(features, values, min(tile, out_h) * min(tile, out_w) * 1.25) if operation == "decode": convolution_phase = ( - convolutions, + *convolutions, WorkingMemoryTerm( "assembled_image", out_h * out_w * _IMAGE_CHANNELS * (_FLUX_VAE_TILED_IMAGE_COPIES * element_size + 1), @@ -377,19 +465,21 @@ def _flux_terms(features: Mapping[str, FeatureValue], values: Mapping[str, float * element_size ) convolution_phase = ( - convolutions, + *convolutions, WorkingMemoryTerm("input_image", out_h * out_w * _IMAGE_CHANNELS * element_size, True), WorkingMemoryTerm("assembled_moments", moments * live_moment_copies, True), ) return _peak_of_two_phases(convolution_phase, cast(int, features["score_matrix_bytes"])) +# The FLUX.1 loaders avoid fp16, which this autoencoder is broken in, except one: an SDNQ VAE folder loads through the +# generic VAE loader and computes in the configured precision, which `auto` makes fp16 on CUDA. VAE_FLUX_SPEC = register( EstimatorSpec( id="vae.flux", - version=1, - coefficients=_AUTOENCODER_KL_COEFFICIENTS, - platform_keys=_CONV_BACKEND_KEYS, + version=2, + coefficients=_convolution_coefficients("fp16", "bf16", "fp32"), + platform_keys=_PLATFORM_KEYS, terms=_flux_terms, ) ) @@ -418,7 +508,7 @@ def estimate_vae_working_memory_flux( backend's coefficients. Defaults to the session device. """ latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1 - param = next(vae.parameters()) + dtype = _compute_dtype(vae) device = device if device is not None else TorchDevice.choose_torch_device() out_h = latent_scale_factor_for_operation * image_tensor.shape[-2] @@ -443,9 +533,9 @@ def estimate_vae_working_memory_flux( "operation": operation, "height": out_h, "width": out_w, - "element_size": param.element_size(), + "dtype": _dtype_name(dtype), "tile": tile, - "score_matrix_bytes": _vae_mid_block_score_matrix_bytes(score_h, score_w, param.dtype, device=device), + "score_matrix_bytes": _vae_mid_block_score_matrix_bytes(score_h, score_w, dtype, device=device), } if tile is not None and operation != "decode": # Read from the VAE rather than hard-coded: an overlap factor of 0.5 would make the tiles @@ -454,7 +544,7 @@ def estimate_vae_working_memory_flux( features["latent_channels"] = getattr(getattr(vae, "config", None), "latent_channels", 16) features["tile_overlap_factor"] = getattr(vae, "tile_overlap_factor", 0.25) - return VAE_FLUX_SPEC.estimate(features, device) + return _estimate(VAE_FLUX_SPEC, features, device) # The FLUX.2 VAE runs one attention block at the bottom of the encoder and one at the top of the @@ -468,29 +558,26 @@ def estimate_vae_working_memory_flux( def _flux2_terms(features: Mapping[str, FeatureValue], values: Mapping[str, float]) -> tuple[WorkingMemoryTerm, ...]: - coefficient = f"k_{features['operation']}" - element_size = cast(int, features["element_size"]) batch_size = cast(int, features["batch_size"]) tile = cast(int | None, features["tile"]) height, width = cast(int, features["height"]), cast(int, features["width"]) if tile is not None: - # The largest slice `AutoencoderKLFlux2` takes: the tile, clamped to the image per axis. - area_bytes = min(tile, height) * min(tile, width) * element_size - # Add 25% for tile overlap and the blending buffers, mirroring the SD1/SDXL estimate. - convolutions = WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes * 1.25 * batch_size, values) + # The largest slice `AutoencoderKLFlux2` takes: the tile, clamped to the image per axis. Add 25% for tile + # overlap and the blending buffers, mirroring the SD1/SDXL estimate. + convolutions = _convolutions(features, values, min(tile, height) * min(tile, width) * 1.25, samples=batch_size) else: - area_bytes = height * width * element_size - convolutions = WorkingMemoryTerm.fitted("convolutions", coefficient, area_bytes * batch_size, values) + convolutions = _convolutions(features, values, height * width, samples=batch_size) # Both terms are per sample; the larger is reserved (see `_peak_of_two_phases` for why, and the measurements). - return _peak_of_two_phases((convolutions,), cast(int, features["score_matrix_bytes"])) + return _peak_of_two_phases(convolutions, cast(int, features["score_matrix_bytes"])) +# The FLUX.2 loaders avoid fp16 (as FLUX.1's do), and none of them goes through the generic VAE loader. VAE_FLUX2_SPEC = register( EstimatorSpec( id="vae.flux2", - version=1, - coefficients=_AUTOENCODER_KL_COEFFICIENTS, - platform_keys=_CONV_BACKEND_KEYS, + version=2, + coefficients=_convolution_coefficients("bf16", "fp32"), + platform_keys=_PLATFORM_KEYS, terms=_flux2_terms, ) ) @@ -505,16 +592,16 @@ def estimate_vae_working_memory_flux2( ) -> WorkingMemoryEstimate: """Estimate the working memory required to encode or decode with the FLUX.2 (32-channel) VAE. - Peak memory scales linearly with pixel area and element size, as it does for the FLUX.1 VAE, and - the implied coefficient is flat across 512-1536px on every backend measured. What is *not* constant - is the coefficient itself: MIOpen's convolution workspaces cost ~1.6x cuDNN's for a decode and ~2.4x - for an encode, so it is shipped per backend -- see `_AUTOENCODER_KL_COEFFICIENTS` for the caveats - and `backend/working_memory/data/vae.json` for the fitted values and their points. Peak *reserved* - memory is what is measured throughout, the conservative quantity that includes allocator overhead. + Peak memory is a line in pixel area per dtype, as it is for the FLUX.1 VAE, from 512 to 2048px on + every backend measured. The line itself is not one: MIOpen's bf16 convolution workspaces cost ~1.5x + cuDNN's for a decode and ~2.4x for an encode, and the allocator and the dtype move it as much, so it + is shipped per platform and dtype -- see `_convolution_coefficients` for the caveats and + `backend/working_memory/data/vae.json` for the fitted values and their points. The points measure + peak *reserved* memory or more, the conservative quantity that includes allocator overhead. - For reference, decoding 1024x1024 peaks at ~4.3GB on cuDNN and ~6.6GB on MIOpen, and 1536x1536 at - ~9.6GB -- far above the default ``device_working_mem_gb``, which is why this estimate must be - passed to the model cache. + For reference, decoding 1024x1024 in bf16 peaks at ~4.3GB on cuDNN and ~6.6GB on MIOpen, and + 1536x1536 at ~9.6GB -- far above the default ``device_working_mem_gb``, which is why this estimate + must be passed to the model cache. That linear term holds only while ``AutoencoderKLFlux2``'s mid-block attention runs through a fused SDPA kernel, which is what CUDA does (verified: the memory-efficient kernel takes the @@ -546,7 +633,7 @@ def estimate_vae_working_memory_flux2( bound. The score matrix is shaped (batch, heads, S, S), so it scales the same way. The encode call sites all pass batch 1 today; the shared estimator does not assume it. """ - param = next(vae.parameters()) + dtype = _compute_dtype(vae) device = device if device is not None else TorchDevice.choose_torch_device() batch_size = image_tensor.shape[0] if image_tensor.dim() >= 4 else 1 latent_scale_factor_for_operation = LATENT_SCALE_FACTOR if operation == "decode" else 1 @@ -570,17 +657,17 @@ def estimate_vae_working_memory_flux2( "width": out_w, "tile": tile, "batch_size": batch_size, - "element_size": param.element_size(), + "dtype": _dtype_name(dtype), "score_matrix_bytes": sdpa_score_matrix_bytes( device=device, - dtype=param.dtype, + dtype=dtype, # The score matrix is (batch, heads, S, S); one head per sample prices the whole batch. num_heads=_FLUX2_VAE_MID_BLOCK_HEADS * batch_size, head_dim=_FLUX2_VAE_MID_BLOCK_HEAD_DIM, seq_len=mid_block_seq_len, ), } - return VAE_FLUX2_SPEC.estimate(features, device) + return _estimate(VAE_FLUX2_SPEC, features, device) def estimate_vae_working_memory_anima( @@ -1077,12 +1164,14 @@ def estimate_vae_working_memory_qwen_image( return int(working_memory) +# The SD3 VAE loads in the configured precision and runs uncast: `auto` gives fp16 on CUDA, and bf16 and fp32 are +# honoured. It carries the family's values in `data/vae.json`, as the one member that runs all three. VAE_SD3_SPEC = register( EstimatorSpec( id="vae.sd3", - version=1, - coefficients=_AUTOENCODER_KL_COEFFICIENTS, - platform_keys=_CONV_BACKEND_KEYS, + version=2, + coefficients=_convolution_coefficients("fp16", "bf16", "fp32"), + platform_keys=_PLATFORM_KEYS, terms=_single_pass_terms, ) ) @@ -1100,4 +1189,4 @@ def estimate_vae_working_memory_sd3( device. """ device = device if device is not None else TorchDevice.choose_torch_device() - return VAE_SD3_SPEC.estimate(_single_pass_features(operation, image_tensor, vae, device), device) + return _estimate(VAE_SD3_SPEC, _single_pass_features(operation, image_tensor, vae, device), device) diff --git a/invokeai/backend/working_memory/coefficients.py b/invokeai/backend/working_memory/coefficients.py index d8e07b13854..a341eb4bca4 100644 --- a/invokeai/backend/working_memory/coefficients.py +++ b/invokeai/backend/working_memory/coefficients.py @@ -1,15 +1,16 @@ """The coefficients estimators are evaluated with, and the measured points that bound them. Both ship as JSON under `data/`, read through `importlib.resources` so a wheel install finds them. A coefficient entry -belongs to one estimator version on one measured platform value, and either carries its values or borrows another -entry's (`values_from`); a point records the features of an operation that was measured and its measured peak. -`tests/backend/working_memory/test_shipped_coefficients.py` resolves every entry against its `EstimatorSpec` and holds -every point to the estimate the shipped coefficients give it. +belongs to one estimator version on one measured platform, and either carries its values or borrows another entry's +(`values_from`); a point records the features of an operation that was measured and its measured peak. A carried value +is the fit of the points it is resolved for (`fit.py`) or a declared stand-in (`stand_ins`). +`tests/backend/working_memory/test_shipped_coefficients.py` resolves every entry against its `EstimatorSpec`, holds +every point to the estimate the shipped coefficients give it, and every fitted value to the fit of its points. """ import functools from collections.abc import Iterable, Mapping -from dataclasses import dataclass +from dataclasses import asdict, dataclass from importlib.resources import files from importlib.resources.abc import Traversable from types import MappingProxyType @@ -18,7 +19,7 @@ from pydantic import BaseModel, ConfigDict, ValidationError, model_validator from invokeai.backend.working_memory.estimate import FeatureValue -from invokeai.backend.working_memory.platform import UNMEASURED_PRICED_AS, Platform +from invokeai.backend.working_memory.platform import Platform, priced_as if TYPE_CHECKING: from invokeai.backend.working_memory.spec import EstimatorSpec @@ -29,7 +30,8 @@ class _DataModel(BaseModel): class ValuesFrom(_DataModel): - """The entry whose values another entry takes: same platform, this estimator id and version.""" + """The entry whose values another entry takes: same platform, this estimator id and version. It may carry more + values than the borrower reads -- a family member that runs fewer dtypes reads fewer of them -- never fewer.""" estimator: str version: int @@ -44,7 +46,15 @@ class CoefficientEntry(_DataModel): values: dict[str, float] | None = None values_from: ValuesFrom | None = None """Borrow the values of another estimator's entry on the same platform, so that its measured points bound this - estimator too. One hop: the entry borrowed from carries its own values.""" + estimator too, and this estimator's points join the fit of those values. One hop: the entry borrowed from carries + its own values.""" + stand_ins: list[str] = [] + """Carried values that are declared conservative stand-ins rather than fits: what this platform prices a line no + in-fit point measures at. `provenance` says what they stand for; every other carried value is a fit.""" + unmeasured_floor: dict[str, float] = {} + """Declared lower bounds, not fits: what carried values are raised to where this entry prices a platform nothing + is measured on (see `priced_as`). A fit describes the platform its points were measured on, not the one it stands + in for. `provenance` says where the floors come from.""" provenance: str """Card, torch build, method and script the values come from, and which points they were fitted on.""" @@ -52,26 +62,43 @@ class CoefficientEntry(_DataModel): def _values_or_values_from(self) -> Self: if (self.values is None) == (self.values_from is None): raise ValueError("a coefficient entry carries exactly one of `values` and `values_from`") + for field, names in (("stand-ins", self.stand_ins), ("unmeasured floors", self.unmeasured_floor)): + unknown = sorted(set(names) - set(self.values or {})) + if unknown: + raise ValueError(f"{field} {unknown} are not values this entry carries") return self class MeasuredPoint(_DataModel): """One measured operation. The estimate for its features must be at least `measured_bytes`, and where a fitted - term decides it, at most 1.3 times that (or `max_ratio`).""" + term decides an in-fit point, at most 1.3 times that (or `max_ratio`). Measured with the model's weights already in + place, so `measured_bytes` is the operation's working memory alone.""" estimator: str version: int platform: dict[str, str] features: dict[str, FeatureValue] measured_bytes: int - method: Literal["reserved_peak"] - """``reserved_peak``: the caching allocator's peak reserved bytes over the operation, one fresh process per - point. Reserved rather than allocated, because reserved is what the model cache's reservation has to cover.""" + method: Literal["reserved_peak", "driver_peak", "demand_peak"] + """How `measured_bytes` was read, each as a peak over its value just before the operation, one fresh process per + point: + + - ``reserved_peak``: the caching allocator's reserved bytes. Reserved rather than allocated, because reserved is + what the model cache's reservation has to cover. + - ``driver_peak``: the process's device usage as the driver reports it (Windows ``D3DKMT`` ``CurrentUsage``), the + larger of two runs. + - ``demand_peak``: the largest of the driver-level, the reserved and the allocated peak, the larger of two runs. + The driver-level figure is what reserved misses: on HIP the first operation of a process maps ~300 MiB beyond + ``max_memory_reserved``.""" device: str provenance: str max_ratio: float | None = None """A tighter upper bound than the default 1.3, as a multiple of `measured_bytes`, for a point the estimate has to stay closer to. It applies whichever term decides the estimate.""" + in_fit: bool = True + """False for a point measured in a regime its platform does not run, kept to check another term: CUDA with the + math attention kernel forced, whose score matrix shares the forward with the convolutions. The fit leaves it out, + and it gets the lower bound only.""" class WorkingMemoryData(_DataModel): @@ -123,21 +150,30 @@ def _where(keys: Mapping[str, str]) -> str: return ",".join(f"{key}={value}" for key, value in keys.items()) -def _entry( - entries: list[CoefficientEntry], estimator: str, version: int, keys: Mapping[str, str], where: str +def find_entry( + entries: Iterable[CoefficientEntry], estimator: str, version: int, platform: Mapping[str, str], where: str ) -> CoefficientEntry: - candidates = [entry for entry in entries if entry.estimator == estimator and entry.platform == keys] + """The entry of `estimator` `version` that applies on `platform`: of those whose every platform key it matches, + the most specific one. An entry that also names the OS thereby takes precedence on that OS over the one that does + not (see `Platform`). Raises, naming `where`, if none applies or the most specific is declared twice.""" + candidates = [ + entry + for entry in entries + if entry.estimator == estimator and all(platform.get(key) == value for key, value in entry.platform.items()) + ] matches = [entry for entry in candidates if entry.version == version] - if len(matches) > 1: - raise LookupError( - f"Working-memory coefficients for estimator '{estimator}' version {version} on {where} are declared " - f"{len(matches)} times." - ) if not matches: found = f" (found only version {sorted({entry.version for entry in candidates})})" if candidates else "" raise LookupError( f"No working-memory coefficients for estimator '{estimator}' version {version} on {where}{found}." ) + most_specific = max(len(entry.platform) for entry in matches) + matches = [entry for entry in matches if len(entry.platform) == most_specific] + if len(matches) > 1: + raise LookupError( + f"Working-memory coefficients for estimator '{estimator}' version {version} on {where} are declared " + f"{len(matches)} times." + ) return matches[0] @@ -146,43 +182,51 @@ def resolve_coefficients( ) -> ResolvedCoefficients: """The values `spec` takes on `platform`, and a `source` that says how they were found. - A platform value nothing is measured for is priced as the measured value `UNMEASURED_PRICED_AS` names. The entry - for the spec's id, version and those platform values either carries the values or borrows them, one hop, from - another estimator's entry on the same platform. No other fallback: an estimate priced with coefficients fitted for - something else is the shortfall this data exists to prevent, so a missing, ambiguous, chained or mismatched entry - raises, naming what is wrong. `origin` prefixes the `source`. + A platform nothing is measured on is priced as the measured platform `priced_as` names. The entry for the spec's + id and version that applies there (`find_entry`) either carries the values or borrows them, one hop, from the + entry of another estimator that applies on the same platform. No other fallback: an estimate priced with + coefficients fitted for something else is the shortfall this data exists to prevent, so a missing, ambiguous, + chained or mismatched entry raises, naming what is wrong. `origin` prefixes the `source`. """ + measured = priced_as(platform) + full = {key: str(value) for key, value in asdict(measured).items()} + keys = {key: full[key] for key in spec.platform_keys} requested = {key: getattr(platform, key) for key in spec.platform_keys} - keys = {key: UNMEASURED_PRICED_AS.get(key, {}).get(value, value) for key, value in requested.items()} where = _where(keys) if keys == requested else f"{_where(keys)} (priced for {_where(requested)})" entries = [entry for file in data for entry in file.coefficients] - entry = _entry(entries, spec.id, spec.version, keys, where) - values, lender = entry.values, None + entry = find_entry(entries, spec.id, spec.version, full, where) + carrier, lender = entry, None if entry.values_from is not None: lender = entry.values_from.estimator - lent = _entry(entries, lender, entry.values_from.version, keys, where) - if lent.values is None: + carrier = find_entry(entries, lender, entry.values_from.version, full, where) + if carrier.values is None: raise LookupError( f"Working-memory coefficients for estimator '{spec.id}' on {where} borrow from '{lender}', whose " "entry borrows in turn; borrow from the entry that carries the values." ) - values = lent.values + values = carrier.values assert values is not None, "a validated entry carries values or values_from" - if set(values) != spec.coefficients: + # An entry's own values are exactly what its spec reads; a lender's may be more (see `ValuesFrom`). + matches = set(values) == spec.coefficients if lender is None else spec.coefficients <= set(values) + if not matches: raise LookupError( f"Working-memory coefficients for estimator '{spec.id}' on {where} provide {sorted(values)}" f"{f' (from {lender})' if lender else ''}; the estimator reads {sorted(spec.coefficients)}." ) - priced = ",".join(value for key, value in keys.items() if value != requested[key]) - if priced and lender: - note = f" ({priced} values of {lender})" - elif priced: - note = f" ({priced} values)" - else: - note = f" (values of {lender})" if lender else "" - return ResolvedCoefficients(values=MappingProxyType(dict(values)), source=f"{origin}:{_where(requested)}{note}") + # The source names the spec's keys, and a refining key (the OS) where an entry it read names one. + shown = [*spec.platform_keys, *(key for key in full if key in {*entry.platform, *carrier.platform})] + shown = list(dict.fromkeys(shown)) + read = {name: values[name] for name in sorted(spec.coefficients)} + notes = [f"values of {lender}"] if lender else [] + if measured != platform: + floors = {name: floor for name, floor in carrier.unmeasured_floor.items() if name in read} + read |= {name: max(read[name], floor) for name, floor in floors.items()} + notes.insert(0, f"priced as {_where({key: full[key] for key in shown})}{', floored' if floors else ''}") + note = f" ({', '.join(notes)})" if notes else "" + source = f"{origin}:{_where({key: str(getattr(platform, key)) for key in shown})}{note}" + return ResolvedCoefficients(values=MappingProxyType(read), source=source) @functools.cache diff --git a/invokeai/backend/working_memory/data/vae.json b/invokeai/backend/working_memory/data/vae.json index bae9081384f..5321cdbec12 100644 --- a/invokeai/backend/working_memory/data/vae.json +++ b/invokeai/backend/working_memory/data/vae.json @@ -3,131 +3,423 @@ "coefficients": [ { "estimator": "vae.sd", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the SD1/SDXL VAE runs the same diffusers AutoencoderKL convolution stack. Not measured on this VAE." + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the SD1/SDXL VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." }, { - "estimator": "vae.sd", - "version": 1, + "estimator": "vae.sd3", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "values": { + "k_decode_fp16": 4597, + "b_decode_fp16": 83886080, + "k_decode_bf16": 4500, + "b_decode_bf16": 68157440, + "k_decode_fp32": 7702, + "b_decode_fp32": 0, + "k_encode_fp16": 2226, + "b_encode_fp16": 22020096, + "k_encode_bf16": 2249, + "b_encode_bf16": 18874368, + "k_encode_fp32": 3236, + "b_encode_fp32": 41943040 + }, + "unmeasured_floor": { + "k_decode_fp16": 4400, + "k_decode_bf16": 4400, + "k_decode_fp32": 8800, + "k_encode_fp16": 2200, + "k_encode_bf16": 2200, + "k_encode_fp32": 4400 + }, + "provenance": "The AutoencoderKL family's convolution lines (bytes per output pixel, and a base in bytes) per operation and dtype, carried by vae.sd3 as the one member that runs all three dtypes; vae.sd, vae.flux and vae.flux2 borrow the lines of theirs. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Fitted on the RTX 4090 points (torch 2.13 and, for the older FLUX.2 rows, 2.7.1) of the SDXL VAE (fp16, fp32), the FLUX.1 VAE (bf16, fp32) and the FLUX.2 VAE (bf16, fp32). Where this entry prices a platform nothing is measured on (MPS, XPU, CPU, or another allocator on this backend), each slope is floored at the figure these estimators priced every dtype at before lines per dtype: 2200 bytes per pixel per element byte for a decode, 1100 for an encode." + }, + { + "estimator": "vae.flux", + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "cudnn", + "allocator": "native" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the SD1/SDXL VAE runs the same diffusers AutoencoderKL convolution stack. Not measured on this VAE." + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.1 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." }, { - "estimator": "vae.sd3", - "version": 1, + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "values_from": { + "estimator": "vae.sd3", + "version": 2 + }, + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.2 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "values": { + "k_decode_bf16": 8696, + "b_decode_bf16": 66060288, + "k_encode_bf16": 2420, + "b_encode_bf16": 22020096 + }, + "unmeasured_floor": { + "k_decode_bf16": 4400, + "k_encode_bf16": 2200 + }, + "provenance": "The CogView4 VAE's own lines: an AutoencoderKL, but not the family's stack (block widths 128/512/1024/1024, three layers per block, no mid-block attention), and a decode costs ~1.9x the family's. bf16 only: its loader forces it. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Fitted on its RTX 4090 points (torch 2.13, native allocator). Where this entry prices a platform nothing is measured on (MPS, XPU, CPU, or another allocator on this backend), each slope is floored at the figure these estimators priced every dtype at before lines per dtype: 2200 bytes per pixel per element byte for a decode, 1100 for an encode." + }, + { + "estimator": "vae.sd", + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the SD3 VAE runs the same diffusers AutoencoderKL convolution stack. Not measured on this VAE." + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the SD1/SDXL VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." }, { "estimator": "vae.sd3", - "version": 1, + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "values": { + "k_decode_fp16": 3797, + "b_decode_fp16": 0, + "k_decode_bf16": 3614, + "b_decode_bf16": 0, + "k_decode_fp32": 5914, + "b_decode_fp32": 13631488, + "k_encode_fp16": 1882, + "b_encode_fp16": 17825792, + "k_encode_bf16": 1882, + "b_encode_bf16": 17825792, + "k_encode_fp32": 2722, + "b_encode_fp32": 3145728 + }, + "provenance": "The AutoencoderKL family's convolution lines (bytes per output pixel, and a base in bytes) per operation and dtype, carried by vae.sd3 as the one member that runs all three dtypes; vae.sd, vae.flux and vae.flux2 borrow the lines of theirs. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Fitted on the RTX 4090 points (torch 2.13, backend:cudaMallocAsync) of the SDXL VAE (fp16, fp32), the FLUX.1 VAE (bf16, fp32) and the FLUX.2 VAE (bf16, fp32)." + }, + { + "estimator": "vae.flux", + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the SD3 VAE runs the same diffusers AutoencoderKL convolution stack. Not measured on this VAE." + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.1 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." }, { - "estimator": "vae.cogview4", - "version": 1, + "estimator": "vae.flux2", + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the CogView4 VAE runs the same diffusers AutoencoderKL convolution stack. Not measured on this VAE." + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.2 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." }, { "estimator": "vae.cogview4", - "version": 1, + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "values": { + "k_decode_bf16": 6765, + "b_decode_bf16": 0, + "k_encode_bf16": 1882, + "b_encode_bf16": 9437184 + }, + "provenance": "The CogView4 VAE's own lines: an AutoencoderKL, but not the family's stack (block widths 128/512/1024/1024, three layers per block, no mid-block attention), and a decode costs ~1.9x the family's. bf16 only: its loader forces it. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Fitted on its RTX 4090 points (torch 2.13, backend:cudaMallocAsync)." + }, + { + "estimator": "vae.sd", + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 + }, + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the SD1/SDXL VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." + }, + { + "estimator": "vae.sd3", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native" }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the CogView4 VAE runs the same diffusers AutoencoderKL convolution stack. Not measured on this VAE." + "values": { + "k_decode_fp16": 7200, + "b_decode_fp16": 0, + "k_decode_bf16": 7143, + "b_decode_bf16": 68157440, + "k_decode_fp32": 14400, + "b_decode_fp32": 0, + "k_encode_fp16": 5500, + "b_encode_fp16": 0, + "k_encode_bf16": 5645, + "b_encode_bf16": 0, + "k_encode_fp32": 11000, + "b_encode_fp32": 0 + }, + "stand_ins": [ + "k_decode_fp16", + "b_decode_fp16", + "k_decode_fp32", + "b_decode_fp32", + "k_encode_fp16", + "b_encode_fp16", + "k_encode_fp32", + "b_encode_fp32" + ], + "unmeasured_floor": { + "k_decode_fp16": 7200, + "k_decode_bf16": 7200, + "k_decode_fp32": 14400, + "k_encode_fp16": 5500, + "k_encode_bf16": 5500, + "k_encode_fp32": 11000 + }, + "provenance": "The AutoencoderKL family's convolution lines (bytes per output pixel, and a base in bytes) per operation and dtype, carried by vae.sd3 as the one member that runs all three dtypes; vae.sd, vae.flux and vae.flux2 borrow the lines of theirs. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Every OS but Windows, which has its own entry. Fitted: bf16 on the FLUX.2 VAE points of Linux with the native allocator (RX 9070 XT gfx1201 and PRO W7900 gfx1100, torch 2.10). Stand-ins, no measurement here: fp16 and fp32 at 3600 decode / 2750 encode bytes per pixel per element byte, no base, the pre-dtype figure. Where this entry prices a platform nothing is measured on (MPS, XPU, CPU, or another allocator on this backend), each slope is floored at the figure these estimators priced every dtype at before lines per dtype: 3600 bytes per pixel per element byte for a decode, 2750 for an encode." }, { "estimator": "vae.flux", - "version": 1, + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native" + }, + "values_from": { + "estimator": "vae.sd3", + "version": 2 + }, + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.1 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native" + }, + "values_from": { + "estimator": "vae.sd3", + "version": 2 + }, + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.2 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native" + }, + "values": { + "k_decode_bf16": 13720, + "b_decode_bf16": 14680064, + "k_encode_bf16": 5500, + "b_encode_bf16": 161480704 + }, + "stand_ins": [ + "k_decode_bf16", + "b_decode_bf16", + "k_encode_bf16", + "b_encode_bf16" + ], + "unmeasured_floor": { + "k_decode_bf16": 7200, + "k_encode_bf16": 5500 + }, + "provenance": "The CogView4 VAE's own lines: an AutoencoderKL, but not the family's stack (block widths 128/512/1024/1024, three layers per block, no mid-block attention), and a decode costs ~1.9x the family's. bf16 only: its loader forces it. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Every OS but Windows, which has its own entry. Not measured here: both lines are stand-ins at its fitted MIOpen expandable-segments lines, as the family's bf16 lines measure the same under the native allocator on Linux and under expandable segments on Windows (7208 vs 7117 MiB decode at 1024px), with each slope raised to the pre-dtype figure where that is larger (the encode). Where this entry prices a platform nothing is measured on (MPS, XPU, CPU, or another allocator on this backend), each slope is floored at the figure these estimators priced every dtype at before lines per dtype: 3600 bytes per pixel per element byte for a decode, 2750 for an encode." + }, + { + "estimator": "vae.sd3", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "values": { + "k_decode_fp16": 3710, + "b_decode_fp16": 274726912, + "k_decode_bf16": 11009, + "b_decode_bf16": 0, + "k_decode_fp32": 10975, + "b_decode_fp32": 0, + "k_encode_fp16": 3703, + "b_encode_fp16": 77594624, + "k_encode_bf16": 4974, + "b_encode_bf16": 1104150528, + "k_encode_fp32": 4114, + "b_encode_fp32": 531628032 + }, + "unmeasured_floor": { + "k_decode_fp16": 7200, + "k_decode_bf16": 7200, + "k_decode_fp32": 14400, + "k_encode_fp16": 5500, + "k_encode_bf16": 5500, + "k_encode_fp32": 11000 + }, + "provenance": "The AutoencoderKL family's convolution lines (bytes per output pixel, and a base in bytes) per operation and dtype, carried by vae.sd3 as the one member that runs all three dtypes; vae.sd, vae.flux and vae.flux2 borrow the lines of theirs. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Windows: fitted on the RX 9060 XT (gfx1200, torch 2.13+rocm10) points of the SDXL VAE (fp16, fp32), the FLUX.1 VAE (bf16, fp32) and the FLUX.2 VAE (bf16, fp32) with expandable segments switched off. The demand is concave in the size, so the envelope over-reserves some points by more than 1.3x; the data test lists them. Where this entry prices a platform nothing is measured on (MPS, XPU, CPU, or another allocator on this backend), each slope is floored at the figure these estimators priced every dtype at before lines per dtype: 3600 bytes per pixel per element byte for a decode, 2750 for an encode." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "values": { + "k_decode_bf16": 17640, + "b_decode_bf16": 0, + "k_encode_bf16": 5060, + "b_encode_bf16": 1059061760 + }, + "unmeasured_floor": { + "k_decode_bf16": 7200, + "k_encode_bf16": 5500 + }, + "provenance": "The CogView4 VAE's own lines: an AutoencoderKL, but not the family's stack (block widths 128/512/1024/1024, three layers per block, no mid-block attention), and a decode costs ~1.9x the family's. bf16 only: its loader forces it. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Windows: fitted on its RX 9060 XT (gfx1200, torch 2.13+rocm10) points with expandable segments switched off. Where this entry prices a platform nothing is measured on (MPS, XPU, CPU, or another allocator on this backend), each slope is floored at the figure these estimators priced every dtype at before lines per dtype: 3600 bytes per pixel per element byte for a decode, 2750 for an encode." + }, + { + "estimator": "vae.sd", + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 + }, + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the SD1/SDXL VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." + }, + { + "estimator": "vae.sd3", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "values": { + "k_decode_fp16": 2016, + "b_decode_fp16": 506462208, + "k_decode_bf16": 6612, + "b_decode_bf16": 529530880, + "k_decode_fp32": 5988, + "b_decode_fp32": 80740352, + "k_encode_fp16": 1960, + "b_encode_fp16": 353370112, + "k_encode_bf16": 3255, + "b_encode_bf16": 483393536, + "k_encode_fp32": 3898, + "b_encode_fp32": 128974848 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the FLUX.1 VAE runs the same diffusers AutoencoderKL convolution stack. The FLUX.1 point below falls inside them." + "provenance": "The AutoencoderKL family's convolution lines (bytes per output pixel, and a base in bytes) per operation and dtype, carried by vae.sd3 as the one member that runs all three dtypes; vae.sd, vae.flux and vae.flux2 borrow the lines of theirs. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Windows only, where expandable segments are Invoke's ROCm default: measured on gfx1200 alone. Elsewhere they are a user override nothing is measured for, priced as MIOpen with the native allocator on that OS. Fitted on the RX 9060 XT (gfx1200, torch 2.13+rocm10) points of the SDXL VAE (fp16, fp32) and the FLUX.1 VAE (bf16, fp32). vae.flux2 carries its own lines here: its fp32 decode differs from the family's by more than the margin." }, { "estimator": "vae.flux", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" }, "values_from": { - "estimator": "vae.flux2", - "version": 1 + "estimator": "vae.sd3", + "version": 2 }, - "provenance": "Borrows the vae.flux2 values, so its points bound this estimator too: the FLUX.1 VAE runs the same diffusers AutoencoderKL convolution stack. Confirmed on the FLUX.1 VAE: RX 9060 XT (gfx1200), torch 2.12, Windows, bf16, 768-1536px, Invoke's ROCm SDPA guard installed, MIOPEN_FIND_MODE=FAST: implied 3451 decode / 2688 encode." + "provenance": "Borrows the lines of its dtypes from vae.sd3, so its points bound them and join their fit: the FLUX.1 VAE runs the same diffusers AutoencoderKL convolution stack. On Windows with MIOpen's native allocator it borrows vae.sd3's Windows entry." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" }, "values": { - "k_decode": 2200, - "k_encode": 1100 + "k_decode_bf16": 6612, + "b_decode_bf16": 529530880, + "k_decode_fp32": 5842, + "b_decode_fp32": 272629760, + "k_encode_bf16": 3255, + "b_encode_bf16": 483393536, + "k_encode_fp32": 3898, + "b_encode_fp32": 128974848 }, - "provenance": "Peak reserved bytes per output pixel per element byte of the diffusers AutoencoderKL convolution stack. Fitted on the FLUX.2 VAE (the vae.flux2 cudnn points) with scripts/calibrate_flux2_working_memory.py --only vae: RTX 4090, torch 2.7.1, bf16, untiled, fused SDPA; implied 2180/2185/2165 decode and 1072/1063/1061 encode at 512/768/1024px, flat across resolution. Rounded up to 2200 / 1100, the pair the FLUX.1 estimator already shipped." + "provenance": "The FLUX.2 VAE's own convolution lines under MIOpen with expandable segments. Windows only, where expandable segments are Invoke's ROCm default: measured on gfx1200 alone. Elsewhere they are a user override nothing is measured for, priced as MIOpen with the native allocator on that OS. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Fitted on its RX 9060 XT (gfx1200, torch 2.13+rocm10) points. Not borrowed from vae.sd3: its fp32 decode needs ~140 MiB more at 512px than the SDXL and FLUX.1 VAEs, which would lift the family's fp32 decode line 9% above theirs, past the 5% margin. Its bf16 lines and fp32 encode equal the family's." }, { - "estimator": "vae.flux2", - "version": 1, + "estimator": "vae.cogview4", + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" }, "values": { - "k_decode": 3600, - "k_encode": 2750 + "k_decode_bf16": 13720, + "b_decode_bf16": 14680064, + "k_encode_bf16": 3764, + "b_encode_bf16": 161480704 }, - "provenance": "Peak reserved bytes per output pixel per element byte of the diffusers AutoencoderKL convolution stack on MIOpen. Fitted on the FLUX.2 VAE (the vae.flux2 miopen points) with scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled: RX 9070 XT (gfx1201) implied 3453/3369/3368 decode, PRO W7900 (gfx1100) 3525 at 512px; encode 2687/2688/2688 on both cards, to the byte. Two AMD generations agreeing that closely make this MIOpen rather than a per-card quirk, so the values are the larger card's plus ~2% headroom: 3600 / 2750. MIOpen's convolution workspaces are larger than cuDNN's (~1.6x decode, ~2.4x encode), identically with fused and math attention, and its encode/decode ratio is 0.78 against cuDNN's 0.49." + "provenance": "The CogView4 VAE's own lines: an AutoencoderKL, but not the family's stack (block widths 128/512/1024/1024, three layers per block, no mid-block attention), and a decode costs ~1.9x the family's. bf16 only: its loader forces it. Fitted lines are working_memory/fit.py's: the upper envelope of the untiled single-sample in-fit points of every estimator resolving to this entry, plus 5%; tiled points only bound their estimates. Windows only, where expandable segments are Invoke's ROCm default: measured on gfx1200 alone. Elsewhere they are a user override nothing is measured for, priced as MIOpen with the native allocator on that OS. Fitted on its RX 9060 XT (gfx1200, torch 2.13+rocm10) points." } ], "points": [ { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -135,19 +427,20 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 1142461301, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -155,19 +448,20 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 2576980378, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -175,19 +469,20 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 4540854174, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -195,19 +490,20 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 561566974, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -215,19 +511,20 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 1254130450, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -235,19 +532,20 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 2224793059, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.7.1, bf16, untiled, fused SDPA (score matrix not materialized). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -255,19 +553,20 @@ "width": 1280, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 7075958620, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, bf16, untiled, fused SDPA: the fused column of the max-versus-sum measurement (#9519). Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, bf16, untiled, fused SDPA: the fused column of the max-versus-sum measurement (#9519). Recorded in GiB to three decimals. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -275,19 +574,20 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 1138753536, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement." + "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -295,19 +595,20 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 2531262464, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement." + "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -315,19 +616,20 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 4466933760, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement." + "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -335,19 +637,20 @@ "width": 1328, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 7493124096, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement." + "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -355,19 +658,20 @@ "width": 1536, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 10043260928, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement." + "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -375,19 +679,20 @@ "width": 1328, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 3168796672, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement." + "provenance": "First FLUX.2 VAE calibration (#9519): CUDA, bf16, untiled, fused SDPA. Recorded in MiB. The decode rows are a separate run from the 512-1024px GiB rows; the 1536px decode is also the fused 9.353 GiB of the max-versus-sum measurement. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -395,19 +700,21 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 285212672 + "score_matrix_bytes": 285212672, + "dtype": "bf16" }, "measured_bytes": 561566974, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB. Standalone script with no allocator configuration: the native allocator.", + "in_fit": false }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -415,19 +722,21 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 1443889152 + "score_matrix_bytes": 1443889152, + "dtype": "bf16" }, "measured_bytes": 1933809025, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB. Standalone script with no allocator configuration: the native allocator.", + "in_fit": false }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -435,19 +744,21 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 4563402752 + "score_matrix_bytes": 4563402752, + "dtype": "bf16" }, "measured_bytes": 4372276707, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB. Standalone script with no allocator configuration: the native allocator.", + "in_fit": false }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -455,19 +766,21 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 4563402752 + "score_matrix_bytes": 4563402752, + "dtype": "bf16" }, "measured_bytes": 3787087413, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB. Standalone script with no allocator configuration: the native allocator.", + "in_fit": false }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -475,19 +788,21 @@ "width": 1280, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 11141120000 + "score_matrix_bytes": 11141120000, + "dtype": "bf16" }, "measured_bytes": 6360846565, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB. Standalone script with no allocator configuration: the native allocator.", + "in_fit": false }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -495,19 +810,21 @@ "width": 1536, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 23102226432 + "score_matrix_bytes": 23102226432, + "dtype": "bf16" }, "measured_bytes": 12847320924, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae with SDPBackend.MATH forced on CUDA -- the regime MPS lands in; bf16, untiled; score_matrix_bytes is the full math-kernel score matrix. Forcing math measures below the fused path until 1536px. The 768px encode sits at the crossover, where the larger term alone is short and the device_working_mem_gb floor covers it; reproducible to three decimals across runs. Recorded in GiB. Standalone script with no allocator configuration: the native allocator.", + "in_fit": false }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "decode", @@ -515,19 +832,20 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 285212672 + "score_matrix_bytes": 285212672, + "dtype": "bf16" }, "measured_bytes": 1810328715, "method": "reserved_peak", "device": "RX 9070 XT (gfx1201)", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "decode", @@ -535,19 +853,20 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 1443889152 + "score_matrix_bytes": 1443889152, + "dtype": "bf16" }, "measured_bytes": 3973918491, "method": "reserved_peak", "device": "RX 9070 XT (gfx1201)", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "decode", @@ -555,19 +874,20 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 4563402752 + "score_matrix_bytes": 4563402752, + "dtype": "bf16" }, "measured_bytes": 7063073718, "method": "reserved_peak", "device": "RX 9070 XT (gfx1201)", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "decode", @@ -575,20 +895,21 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 285212672 + "score_matrix_bytes": 285212672, + "dtype": "bf16" }, "measured_bytes": 1847909679, "method": "reserved_peak", "device": "PRO W7900 (gfx1100)", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals.", + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it.", "max_ratio": 1.15 }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "decode", @@ -596,20 +917,21 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 1443889152 + "score_matrix_bytes": 1443889152, + "dtype": "bf16" }, "measured_bytes": 4059817837, "method": "reserved_peak", "device": "PRO W7900 (gfx1100)", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals.", + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it.", "max_ratio": 1.15 }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "decode", @@ -617,20 +939,21 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 4563402752 + "score_matrix_bytes": 4563402752, + "dtype": "bf16" }, "measured_bytes": 7197291446, "method": "reserved_peak", "device": "PRO W7900 (gfx1100)", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals.", + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it.", "max_ratio": 1.15 }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "encode", @@ -638,19 +961,20 @@ "width": 512, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 285212672 + "score_matrix_bytes": 285212672, + "dtype": "bf16" }, "measured_bytes": 1408749273, "method": "reserved_peak", "device": "RX 9070 XT (gfx1201) and PRO W7900 (gfx1100), identical to the byte", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "encode", @@ -658,19 +982,20 @@ "width": 768, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 1443889152 + "score_matrix_bytes": 1443889152, + "dtype": "bf16" }, "measured_bytes": 3170759606, "method": "reserved_peak", "device": "RX 9070 XT (gfx1201) and PRO W7900 (gfx1100), identical to the byte", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "miopen" + "conv_backend": "miopen", + "allocator": "native" }, "features": { "operation": "encode", @@ -678,19 +1003,20 @@ "width": 1024, "tile": null, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 4563402752 + "score_matrix_bytes": 4563402752, + "dtype": "bf16" }, "measured_bytes": 5637144576, "method": "reserved_peak", "device": "RX 9070 XT (gfx1201) and PRO W7900 (gfx1100), identical to the byte", - "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals." + "provenance": "scripts/calibrate_flux2_working_memory.py --only vae, torch 2.10, bf16, untiled; a clean run (no MIOPEN_FIND_MODE override, no allocator GC threshold). score_matrix_bytes is the math-kernel score matrix the ROCm head-dim guard prices for the 512-wide head, unchunked; on these cards forcing math moved the peak by nothing up to 1024px. Recorded in GiB to three decimals. Linux, no allocator configuration: the native allocator, as the script ran it." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -698,19 +1024,20 @@ "width": 1024, "tile": null, "batch_size": 2, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 8541616210, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "#9519: 1024px decode by latent batch, CUDA, bf16, fused SDPA, one fresh process per point: 4.23 / 7.96 / 11.89 GiB at batch 1 / 2 / 3. The test table wrote them as GiB x 1000 (4229, 7955, 11889); batch 1 is the 4.229 GiB 1024px decode above." + "provenance": "#9519: 1024px decode by latent batch, CUDA, bf16, fused SDPA, one fresh process per point: 4.23 / 7.96 / 11.89 GiB at batch 1 / 2 / 3. The test table wrote them as GiB x 1000 (4229, 7955, 11889); batch 1 is the 4.229 GiB 1024px decode above. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "decode", @@ -718,19 +1045,20 @@ "width": 1024, "tile": null, "batch_size": 3, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 12765716546, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "#9519: 1024px decode by latent batch, CUDA, bf16, fused SDPA, one fresh process per point: 4.23 / 7.96 / 11.89 GiB at batch 1 / 2 / 3. The test table wrote them as GiB x 1000 (4229, 7955, 11889); batch 1 is the 4.229 GiB 1024px decode above." + "provenance": "#9519: 1024px decode by latent batch, CUDA, bf16, fused SDPA, one fresh process per point: 4.23 / 7.96 / 11.89 GiB at batch 1 / 2 / 3. The test table wrote them as GiB x 1000 (4229, 7955, 11889); batch 1 is the 4.229 GiB 1024px decode above. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux2", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", @@ -738,32 +1066,5247 @@ "width": 2024, "tile": 512, "batch_size": 1, - "element_size": 2, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 585105408, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "#9519: reference-image encode at the 2024px reference cap with the forced 512px tile, CUDA, bf16, fused SDPA; flat at ~0.55 GB from 1024px up. Recorded in MiB." + "provenance": "#9519: reference-image encode at the 2024px reference cap with the forced 512px tile, CUDA, bf16, fused SDPA; flat at ~0.55 GB from 1024px up. Recorded in MiB. Standalone script with no allocator configuration: the native allocator." }, { "estimator": "vae.flux", - "version": 1, + "version": 2, "platform": { - "conv_backend": "cudnn" + "conv_backend": "cudnn", + "allocator": "native" }, "features": { "operation": "encode", "height": 3072, "width": 3072, - "element_size": 2, "tile": null, - "score_matrix_bytes": 0 + "score_matrix_bytes": 0, + "dtype": "bf16" }, "measured_bytes": 20229295964, "method": "reserved_peak", "device": "RTX 4090", - "provenance": "Commit 326bdf1bd0: the FLUX.1 VAE as a diffusers AutoencoderKL, bf16, fused SDPA, untiled 3072px encode: 18.84 GiB. Re-measured 18.57 GiB on torch 2.13+cu130, 2026-10-17, with scripts/calibrate_flux_vae_working_memory.py." + "provenance": "Commit 326bdf1bd0: the FLUX.1 VAE as a diffusers AutoencoderKL, bf16, fused SDPA, untiled 3072px encode: 18.84 GiB. Re-measured 18.57 GiB on torch 2.13+cu130, 2026-10-17, with scripts/calibrate_flux_vae_working_memory.py. Standalone script with no allocator configuration: the native allocator." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1488977920, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3334471680, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5817499648, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 9105833984, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1744830464, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4326424576, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7572815872, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 11741954048, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 891289600, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2132803584, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3709861888, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5689573376, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1226833920, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2661285888, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4622123008, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7157579776, + "method": "driver_peak", + "device": "RTX 4090", + "provenance": "Isolated SDXL VAE decode (diffusers AutoencoderKL, force_upcast; random latents as l2i), one fresh process per size, the larger of two decodes: RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, fused SDPA, native allocator. Driver-level usage agreed with max_memory_reserved within 100 MiB. A game held ~3 GB on the card meanwhile; no paging measured, allocated identical. bf16 measured identical to fp16 (not shipped: this node never runs bf16). Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1140850688, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4538236928, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 10158604288, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 2048, + "width": 2048, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 18029215744, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 562036736, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2225078272, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4989124608, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 2048, + "width": 2048, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 8864661504, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 3072, + "width": 3072, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 19937624064, + "method": "reserved_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux_vae_working_memory.py (2026-10-17): the FLUX.1 ae.safetensors as a diffusers AutoencoderKL, RTX 4090, torch 2.13.0+cu130, Windows, bf16, fused SDPA, untiled, native allocator (no allocator configuration). The untiled 3072px decode ran into the card's limit and is not shipped." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 985661440, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 1614807040, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2495610880, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3271557120, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 825229312, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 1433403392, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2293235712, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3040870400, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1570766848, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3355443200, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5997854720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 9418309632, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1095761920, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2312110080, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3900702720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6083837952, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2154823680, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3984588800, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6983516160, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10821304320, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1197473792, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2287992832, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3546284032, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5538578432, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1570766848, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3355443200, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5997854720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 9418309632, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1095761920, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2312110080, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3900702720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6083837952, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2154823680, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3984588800, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6983516160, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10821304320, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1197473792, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2287992832, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3548381184, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5538578432, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1717567488, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3376414720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6018826240, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 9374269440, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1095761920, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2312110080, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3900702720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6081740800, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1186988032, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2344615936, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3212836864, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 4796186624, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 998244352, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2153775104, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2526019584, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3462397952, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2686451712, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6155141120, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10959716352, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 16733175808, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1533018112, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2495610880, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 4433379328, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6924795904, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2176843776, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5135925248, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10993270784, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 16787701760, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2293235712, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3206545408, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5639241728, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 8812232704, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2684354560, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6155141120, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10959716352, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 16733175808, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1533018112, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2495610880, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 4433379328, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6922698752, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2178940928, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5135925248, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10993270784, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 16789798912, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2293235712, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 3206545408, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 5641338880, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 8810135552, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 2684354560, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6155141120, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 10959716352, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 16689135616, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 285212672 + }, + "measured_bytes": 1533018112, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 2493513728, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 4431282176, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 1073741824 + }, + "measured_bytes": 6922698752, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1157627904, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2592079872, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4552916992, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 10173284352, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 576716800, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1268776960, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2241855488, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5022679040, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1675624448, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4257218560, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7503609856, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 16770924544, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 847249408, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1843396608, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3261071360, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7308574720, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1157627904, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2592079872, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4555014144, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 10175381504, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 576716800, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1270874112, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2239758336, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5005901824, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1675624448, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4259315712, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7501512704, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 16770924544, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 845152256, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1843396608, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3261071360, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7306477568, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1159725056, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2592079872, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4555014144, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 10057940992, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 578813952, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1270874112, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2241855488, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5005901824, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1675624448, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4257218560, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7501512704, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 16791896064, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 847249408, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1841299456, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3258974208, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 7306477568, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 822083584, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2063597568, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3640655872, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 8069840896, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 486539264, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1056964608, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1895825408, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp16", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4244635648, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1442840576, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3265265664, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5748293632, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 12996050944, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 681574400, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1522532352, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2694840320, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.sd", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "vae_class": "AutoencoderKL", + "upcast_weight_bytes": 0, + "score_matrix_bytes": 0 + }, + "measured_bytes": 6083837952, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init SDXL VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 788529152, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2030043136, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3607101440, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 8036286464, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 486539264, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1056964608, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1895825408, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4244635648, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1442840576, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3265265664, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5714739200, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 12962496512, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 681574400, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1520435200, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2694840320, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "score_matrix_bytes": 0 + }, + "measured_bytes": 6083837952, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.1 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 788529152, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2030043136, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3607101440, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 8069840896, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 486539264, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1073741824, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1895825408, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 4244635648, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1409286144, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 3265265664, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 5781848064, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 12962496512, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 681574400, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 1520435200, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 2694840320, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "fp32", + "tile": null, + "batch_size": 1, + "score_matrix_bytes": 0 + }, + "measured_bytes": 6117392384, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init FLUX.2 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 2233466880, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 4947181568, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 8690597888, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16" + }, + "measured_bytes": 19386073088, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 624951296, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 1379926016, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 2436890624, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16" + }, + "measured_bytes": 5454692352, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, native allocator, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 1644167168, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 3800039424, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 6685720576, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "decode", + "height": 1536, + "width": 1536, + "dtype": "bf16" + }, + "measured_bytes": 14738784256, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 478150656, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 1048576000, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 1887436800, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "operation": "encode", + "height": 1536, + "width": 1536, + "dtype": "bf16" + }, + "measured_bytes": 4236247040, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RTX 4090, torch 2.13.0+cu130, Windows, driver 32.0.16.1047, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 3439329280, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 7717519360, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 13715374080, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 1092616192, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 2267021312, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 3755999232, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "bf16" + }, + "measured_bytes": 5874122752, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (torch reports the native backend), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (10040 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 4399824896, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 9909043200, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "decode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 17616076800, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 512, + "width": 512, + "dtype": "bf16" + }, + "measured_bytes": 2271215616, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 768, + "width": 768, + "dtype": "bf16" + }, + "measured_bytes": 3206545408, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1024, + "width": 1024, + "dtype": "bf16" + }, + "measured_bytes": 5695864832, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." + }, + { + "estimator": "vae.cogview4", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "operation": "encode", + "height": 1280, + "width": 1280, + "dtype": "bf16" + }, + "measured_bytes": 8902410240, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "Scratch probe vae_dtype_probe.py (2026-10-17): RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0, Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, Invoke's ROCm SDPA guard installed, so score_matrix_bytes is its chunked math term. Windows-only: the native allocator's demand there is concave (first-run driver spikes at small sizes, a second run reserving up to 1.6x the first at large ones) and up to 1.6x Linux's. Random-init CogView4 VAE from the released config (memory does not depend on weight values), untiled, one fresh process per point, two runs, no allocator retries. Recorded in MiB. The 1280px decode is not shipped: it needs ~20 GB on this 16 GB card, which Windows pages into system memory instead of failing, so it read low (16650 MiB)." } ] } diff --git a/invokeai/backend/working_memory/fit.py b/invokeai/backend/working_memory/fit.py new file mode 100644 index 00000000000..8ca220c0f1c --- /dev/null +++ b/invokeai/backend/working_memory/fit.py @@ -0,0 +1,151 @@ +"""The shipped fit: the values under `data/` reproduced from their measured points. + +The AutoencoderKL estimators price their convolutions as a line in the output's pixel count, one per operation and +dtype: `k__` bytes per pixel plus `b__` bytes, the base a process pays whatever the +size. Each carried pair is the upper envelope of the points it is resolved for (see `upper_envelope`), plus +`SHIPPED_MARGIN`. A point joins the fit of the entry its estimator resolves to on its platform -- the lender's, where +the estimator borrows -- when it is a single-sample, untiled operation of that pair (`in_fit` unless measured out of +regime) with no weight growth. Tiled points bound their estimates but fit nothing: the tile term reuses the untiled +pair. Nor does a point whose VAE an in-place cast grew (`upcast_weight_bytes`): its demand includes the growth, which +the estimate prices as a term of its own. + +`tests/backend/working_memory/test_shipped_coefficients.py` holds every carried value that is not a declared stand-in +to `fitted_values` of the shipped points, so a new point that moves a fit fails there with the value to ship. +""" + +import math +from collections.abc import Iterable, Mapping, Sequence +from fractions import Fraction +from typing import Final + +from invokeai.backend.working_memory.coefficients import CoefficientEntry, MeasuredPoint, WorkingMemoryData, find_entry + +SHIPPED_MARGIN: Final = Fraction(1, 20) +"""What the shipped slope and intercept add over the envelope of their points: 5%, for the runs and cards the points +do not cover.""" + +MIN_AREA_RATIO: Final = 2 +"""How far apart the sizes a line is fitted on must lie, as a ratio of pixel counts: closer points pin the intercept +no better than their noise.""" + +_MIB: Final = 2**20 + +EntryKey = tuple[str, int, tuple[tuple[str, str], ...]] +"""An entry's estimator id, version and platform keys: what a point is resolved by.""" + + +def entry_key(entry: CoefficientEntry | MeasuredPoint) -> EntryKey: + return (entry.estimator, entry.version, tuple(sorted(entry.platform.items()))) + + +def upper_envelope(points: Sequence[tuple[int, int]]) -> tuple[Fraction, Fraction]: + """The line ``y = k * x + b`` on or above every ``(x, y)`` point, with ``b >= 0``, that over-reserves least. + + The candidates are the edges of the upper convex hull of the points and the origin: each such line lies on or + above every point, and the origin keeps its intercept non-negative. Of those, the one whose largest ratio to the + highest reading of a size is smallest wins, the steeper one on a tie, since it is the more conservative past the + largest point. Only the highest reading counts: a lower one at a measured size is covered whatever the line, and + rating against it would tilt the line though no point lies above it. Raises unless the points span two sizes at + least `MIN_AREA_RATIO` apart. + """ + if not points or any(x <= 0 or y <= 0 for x, y in points): + raise ValueError(f"an envelope needs points with positive sizes and bytes, got {list(points)}") + sizes = sorted({x for x, _ in points}) + if len(sizes) < 2 or sizes[-1] < MIN_AREA_RATIO * sizes[0]: + raise ValueError( + f"an envelope needs two sizes at least {MIN_AREA_RATIO}x apart in pixels, got {sizes}; measure more sizes" + ) + + highest = {x: max(y for px, y in points if px == x) for x in sizes} + hull: list[tuple[int, int]] = [(0, 0)] + for point in highest.items(): + # Drop the last vertex while it is not above the chord from the one before it to this point. + while len(hull) >= 2: + (x0, y0), (x1, y1) = hull[-2], hull[-1] + if (x1 - x0) * (point[1] - y0) - (y1 - y0) * (point[0] - x0) < 0: + break + hull.pop() + hull.append(point) + + best: tuple[Fraction, Fraction, Fraction] | None = None + for (x0, y0), (x1, y1) in zip(hull, hull[1:], strict=False): + k = Fraction(y1 - y0, x1 - x0) + b = y0 - k * x0 + if k < 0: + continue + worst = max((k * x + b) / y for x, y in highest.items()) + if best is None or (worst, -k) < (best[0], -best[1]): + best = (worst, k, b) + assert best is not None, "the edge from the origin always rises" + return best[1], best[2] + + +def shipped_pair(points: Sequence[tuple[int, int]]) -> tuple[int, int]: + """The slope and intercept to ship for these points: the envelope plus `SHIPPED_MARGIN`, the slope rounded up to + a whole byte per pixel and the intercept to a whole MiB.""" + k, b = upper_envelope(points) + margin = 1 + SHIPPED_MARGIN + return math.ceil(k * margin), math.ceil(b * margin / _MIB) * _MIB + + +def _pairs(entry: CoefficientEntry) -> set[str]: + """The `_` suffixes an entry carries both halves of a convolution line for.""" + names = set(entry.values or {}) + return {name[2:] for name in names if name.startswith("k_") and f"b_{name[2:]}" in names} + + +def fit_points(data: Iterable[WorkingMemoryData]) -> Mapping[tuple[EntryKey, str], list[tuple[int, int]]]: + """Per carried entry and line, the ``(pixels, bytes)`` points that line is fitted on. + + A point belongs to the entry its own estimator's entry resolves to on its platform, one `values_from` hop, and to + the line its operation and dtype name. Lines no point measures are absent. + """ + files = list(data) + entries = [entry for file in files for entry in file.coefficients] + groups: dict[tuple[EntryKey, str], list[tuple[int, int]]] = {} + for point in (point for file in files for point in file.points): + features = point.features + if ( + not point.in_fit + or features.get("tile") is not None + or features.get("batch_size", 1) != 1 + or features.get("upcast_weight_bytes", 0) + ): + continue + owner = carrier_of(entries, point) + suffix = f"{features.get('operation')}_{features.get('dtype')}" + if suffix in _pairs(owner): + pixels = int(features["height"]) * int(features["width"]) # type: ignore[arg-type] + groups.setdefault((entry_key(owner), suffix), []).append((pixels, point.measured_bytes)) + return groups + + +def carrier_of(entries: Sequence[CoefficientEntry], point: MeasuredPoint) -> CoefficientEntry: + """The entry that carries the values `point` is evaluated with: its estimator's entry on its platform, or the one + that entry borrows from. A point names the OS only where it measured an OS-specific entry's platform.""" + where = f"{point.estimator} point on {point.platform}" + entry = find_entry(entries, point.estimator, point.version, point.platform, where) + if entry.values_from is None: + return entry + return find_entry(entries, entry.values_from.estimator, entry.values_from.version, point.platform, where) + + +def fitted_values(data: Iterable[WorkingMemoryData]) -> Mapping[EntryKey, Mapping[str, int]]: + """Per entry that carries values, what `shipped_pair` gives each line it does not declare a stand-in. + + Raises, naming the entry and line, where the points cannot fit that line. + """ + files = list(data) + groups = fit_points(files) + fitted: dict[EntryKey, dict[str, int]] = {} + for entry in (entry for file in files for entry in file.coefficients if entry.values is not None): + key = entry_key(entry) + for suffix in sorted(_pairs(entry)): + if {f"k_{suffix}", f"b_{suffix}"} <= set(entry.stand_ins): + continue + try: + k, b = shipped_pair(groups.get((key, suffix), [])) + except ValueError as e: + raise ValueError(f"{entry.estimator} on {entry.platform}, {suffix}: {e}") from e + fitted.setdefault(key, {}).update({f"k_{suffix}": k, f"b_{suffix}": b}) + return fitted diff --git a/invokeai/backend/working_memory/platform.py b/invokeai/backend/working_memory/platform.py index 5dfc2a00997..8ceafbd9626 100644 --- a/invokeai/backend/working_memory/platform.py +++ b/invokeai/backend/working_memory/platform.py @@ -1,32 +1,90 @@ -from collections.abc import Mapping -from dataclasses import dataclass -from types import MappingProxyType +import sys +from dataclasses import astuple, dataclass, replace from typing import Final, Literal import torch +from invokeai.app.util.torch_cuda_allocator import expandable_segments_enabled + ConvBackend = Literal["cudnn", "miopen", "mps", "xpu", "cpu"] +Allocator = Literal["native", "cudaMallocAsync", "expandable"] +"""torch's caching allocator: the native one, the native one in expandable-segments mode, or the driver's +``cudaMallocAsync`` pool (``hipMallocAsync`` on a HIP build, which torch reports under the same name).""" +OperatingSystem = Literal["windows", "linux", "other"] @dataclass(frozen=True) class Platform: - """The traits of the machine an operation runs on, as coefficients are keyed. Each field is a platform key an - `EstimatorSpec` can name, and holds the true value: what was measured, and what was not, is `UNMEASURED_PRICED_AS`'s - business.""" + """The traits of the machine an operation runs on, as coefficients are keyed. Each field holds the true value: + what was measured, and what was not, is `priced_as`'s business. + + `conv_backend` and `allocator` are the keys an `EstimatorSpec` names. `os` refines them only where an entry says + so: an entry whose platform names an `os` applies there and takes precedence over the entry that does not, so the + operating system enters the pricing of exactly the platforms measured on one OS or measured to differ by it, and + of no other. Both are MIOpen: its native allocator demands up to 1.6x more on Windows than on Linux, and expandable + segments, Invoke's Windows default, are measured only on Windows. + """ conv_backend: ConvBackend + allocator: Allocator + os: OperatingSystem -UNMEASURED_PRICED_AS: Final[Mapping[str, Mapping[str, str]]] = MappingProxyType( +MEASURED: Final = frozenset( { - # MPS, XPU and CPU convolutions are unmeasured and take the cuDNN coefficients. On MPS that pairs with a - # score-matrix term the SDPA probe always charges there, so the total is not thin. - "conv_backend": MappingProxyType({"mps": "cudnn", "xpu": "cudnn", "cpu": "cudnn"}), + ("cudnn", "native", None), + ("cudnn", "cudaMallocAsync", None), + ("miopen", "native", None), + ("miopen", "expandable", "windows"), } ) -"""Per platform key, the values no coefficients are measured for, and the measured value each is priced as. The one -place that borrowing is declared: the resolver applies it and names it in an estimate's `source`, and shipped data -carries entries for measured values only.""" +"""The (convolution backend, allocator, OS) combinations coefficients are measured on, None for every OS. Shipped data +carries entries for these only.""" + + +def is_measured(platform: Platform) -> bool: + return {(platform.conv_backend, platform.allocator, None), astuple(platform)} & MEASURED != set() + + +def priced_as(platform: Platform) -> Platform: + """The measured platform an operation on `platform` is priced as: `platform` itself where it is measured. The OS + stays: it only selects among the entries of the measured pair. + + The one place that borrowing is declared; the resolver applies it and names it in an estimate's `source`. + + - A convolution backend nothing is measured on (MPS, XPU, CPU) takes cuDNN under the native allocator. On MPS that + pairs with a score-matrix term the SDPA probe always charges there, so the total is not thin. + - An allocator not measured with its backend on this OS takes that backend's native allocator: cuDNN in + expandable-segments mode, MIOpen under ``hipMallocAsync``, and MIOpen with expandable segments off Windows (a + user override there; Linux defaults to native). On cuDNN, native is the larger of the two measured allocators + (``cudaMallocAsync`` needs 25-35% less); on MIOpen, native is the larger too, on Windows by up to 1.6x. + """ + if is_measured(platform): + return platform + if platform.conv_backend in ("mps", "xpu", "cpu"): + return replace(platform, conv_backend="cudnn", allocator="native") + return replace(platform, allocator="native") + + +def _allocator_for(device: torch.device) -> Allocator: + """The caching allocator an operation on `device` allocates through: `native` for anything but a CUDA/HIP GPU. + + The backend is fixed before torch initializes CUDA, so torch's answer holds for the process; it is asked first, + because the driver's pool ignores expandable segments. Expandable segments are a mode of the native allocator, read + from the environment the model cache reads them from. A ``cuda`` device on a build without one (a test pinning the + device) has no backend to ask. + """ + if device.type != "cuda": + return "native" + if torch.cuda.is_available() and torch.cuda.get_allocator_backend() == "cudaMallocAsync": + return "cudaMallocAsync" + return "expandable" if expandable_segments_enabled() else "native" + + +def _host_os() -> OperatingSystem: + if sys.platform == "win32": + return "windows" + return "linux" if sys.platform.startswith("linux") else "other" def platform_for(device: torch.device) -> Platform: @@ -36,10 +94,11 @@ def platform_for(device: torch.device) -> Platform: MIOpen from cuDNN, and a `cpu_only` model on a ROCm build is not on MIOpen. Any device type other than CUDA, MPS and XPU runs torch's CPU kernels, or none at all, and is priced as the CPU. """ + allocator, os = _allocator_for(device), _host_os() if device.type == "cuda": - return Platform(conv_backend="miopen" if torch.version.hip is not None else "cudnn") + return Platform("miopen" if torch.version.hip is not None else "cudnn", allocator, os) if device.type == "mps": - return Platform(conv_backend="mps") + return Platform("mps", allocator, os) if device.type == "xpu": - return Platform(conv_backend="xpu") - return Platform(conv_backend="cpu") + return Platform("xpu", allocator, os) + return Platform("cpu", allocator, os) diff --git a/scripts/calibrate_flux2_working_memory.py b/scripts/calibrate_flux2_working_memory.py index c429c5b9a49..783fe00d04f 100644 --- a/scripts/calibrate_flux2_working_memory.py +++ b/scripts/calibrate_flux2_working_memory.py @@ -7,8 +7,9 @@ 1. ``SDPA_MATH_BYTES_PER_SCORE_ELEMENT`` (``backend/util/attention.py``) -- bytes per element of the score matrix, charged only where SDPA has no fused kernel and materializes it. -2. ``estimate_vae_working_memory_flux2``'s ``k_decode`` / ``k_encode`` bytes per pixel per element byte, - shipped per convolution backend in ``backend/working_memory/data/vae.json``. +2. ``estimate_vae_working_memory_flux2``'s convolution lines, ``k__`` bytes per pixel plus a + ``b__`` base, shipped per convolution backend and allocator in + ``backend/working_memory/data/vae.json``. 3. ``Flux2DenoiseInvocation._estimate_working_memory``'s 0.4 MB per token, defined at the Klein 9B width (4096) and scaled linearly by the loaded variant's width. 4. The dispatch question underneath all of it: does this build's SDPA fuse these shapes, or does it @@ -75,9 +76,12 @@ MIB = 1024**2 -def _shipped_vae_coefficient(operation: str) -> float: - """The VAE coefficient this build's estimate uses for `operation`: the column its convolution backend selects.""" - return shipped_coefficients(VAE_FLUX2_SPEC, platform_for(torch.device("cuda"))).values[f"k_{operation}"] +def _shipped_vae_line(operation: str, dtype_name: str) -> tuple[float, float]: + """The VAE line this build's estimate uses for `operation` in `dtype_name`: bytes per pixel and base bytes, as the + platform (convolution backend, allocator) selects them.""" + dtype = {"float16": "fp16", "bfloat16": "bf16", "float32": "fp32"}[dtype_name] + values = shipped_coefficients(VAE_FLUX2_SPEC, platform_for(torch.device("cuda"))).values + return values[f"k_{operation}_{dtype}"], values[f"b_{operation}_{dtype}"] # The FLUX.2 VAE attends on the 8x-downsampled grid with a single 512-wide head. Mirrors @@ -285,13 +289,13 @@ def run() -> None: "reserved_delta": peak, "estimate": estimate, "score_term": score_bytes, - # The whole measured peak over the pixel area, which is what the `k_decode`/`k_encode` - # coefficients name. The score matrix is deliberately NOT backed out: the two terms peak in different + # The whole measured peak over the pixel area: the line's bytes per pixel plus its base spread over + # that area. The score matrix is deliberately NOT backed out: the two terms peak in different # phases of the forward, so the estimate takes the larger of them rather than the sum, and # the convolution phase is what sets the high-water mark until the quadratic term overtakes # it past ~1280px. Where `score_term` exceeds the measured peak, this column is not the # quantity being fitted -- the score model is, and it is bounding rather than describing. - "implied_linear_constant": peak / (px * px * element_size), + "implied_linear_constant": peak / (px * px), "covered": peak <= estimate, } @@ -464,10 +468,12 @@ def report_sdpa(shapes: list[tuple[int, int, int]], dtype_name: str) -> list[dic def report_vae(pxs: list[int], dtype_name: str, vae_path: str | None) -> list[dict]: - decode_k = _shipped_vae_coefficient("decode") - encode_k = _shipped_vae_coefficient("encode") - print(f"\n=== 3. VAE linear coefficients (this build selects {decode_k:.0f} decode / {encode_k:.0f} encode) ===") - print("`implied_k` is the measured peak over pixel area, directly comparable to those coefficients;") + (decode_k, decode_b), (encode_k, encode_b) = (_shipped_vae_line(op, dtype_name) for op in ("decode", "encode")) + print( + f"\n=== 3. VAE lines (this build selects {decode_k:.0f} B/px + {decode_b / MIB:.0f} MiB decode, " + f"{encode_k:.0f} B/px + {encode_b / MIB:.0f} MiB encode) ===" + ) + print("`implied_k` is the measured peak over pixel area, comparable to those bytes per pixel plus the base;") print("fit on the rows whose `math` column matches what this build really does (section 1).") print("`covered` is the question that matters: is the shipped estimate an upper bound here?") print("Caveat: forcing math on a build that HAS a fused kernel is not equivalent to a build that") @@ -502,10 +508,10 @@ def report_vae(pxs: list[int], dtype_name: str, vae_path: str | None) -> list[di _flag_non_monotonic(rows) for operation in ("decode", "encode"): # Compare against the column this build actually selects, not against a hard-coded number. - shipped = _shipped_vae_coefficient(operation) + shipped_k, shipped_b = _shipped_vae_line(operation, dtype_name) for force_math in (False, True): ks = [ - r["implied_linear_constant"] + r["implied_linear_constant"] - shipped_b / (r["px"] * r["px"]) for r in rows if r["operation"] == operation and r["force_math"] is force_math @@ -517,8 +523,11 @@ def report_vae(pxs: list[int], dtype_name: str, vae_path: str | None) -> list[di if not ks: continue mode = "math" if force_math else "fused" - verdict = "OK" if shipped >= max(ks) else "SHORT" - print(f" {operation} ({mode}): implied_k max = {max(ks):.0f}, shipped = {shipped:.0f} -> {verdict}") + verdict = "OK" if shipped_k >= max(ks) else "SHORT" + print( + f" {operation} ({mode}): implied_k max past the base = {max(ks):.0f}, shipped = {shipped_k:.0f} " + f"-> {verdict}" + ) short = [r for r in rows if not r.get("oom") and not r["covered"]] if short: print("\n Points the shipped estimate does NOT cover:") diff --git a/tests/app/invocations/test_flux2_working_memory.py b/tests/app/invocations/test_flux2_working_memory.py index 0737d216bdd..566529feefa 100644 --- a/tests/app/invocations/test_flux2_working_memory.py +++ b/tests/app/invocations/test_flux2_working_memory.py @@ -44,9 +44,10 @@ GB = 1024**3 -def _vae_k(conv_backend, operation): - """The shipped FLUX.2 VAE coefficient the estimator resolves for one convolution backend and operation.""" - return shipped_coefficients(VAE_FLUX2_SPEC, Platform(conv_backend)).values[f"k_{operation}"] +def _vae_line(conv_backend, operation, pixels, allocator="native", os="linux"): + """What the shipped FLUX.2 VAE line reserves in bf16 over `pixels` output pixels on one platform.""" + values = shipped_coefficients(VAE_FLUX2_SPEC, Platform(conv_backend, allocator, os)).values + return int(values[f"k_{operation}_bf16"] * pixels + values[f"b_{operation}_bf16"]) # The estimator's default width, and the head count that follows from it. The suite's pinned @@ -280,7 +281,7 @@ def test_the_lora_margin_scales_with_the_batch(self): class TestFlux2VaeWorkingMemoryEstimate: def _mock_bf16_vae(self): vae = MagicMock(spec=AutoencoderKLFlux2) - vae.parameters.return_value = iter([torch.zeros(1, dtype=torch.bfloat16)]) # element_size == 2 + vae.parameters.return_value = iter([torch.zeros(1, dtype=torch.bfloat16)]) return vae def _tensor_for(self, operation, px): @@ -288,11 +289,11 @@ def _tensor_for(self, operation, px): return torch.zeros(1, 32, px // 8, px // 8) if operation == "decode" else torch.zeros(1, 3, px, px) @pytest.mark.parametrize("operation", ["decode", "encode"]) - def test_constant_scales_pixel_area_and_element_size(self, operation): + def test_the_estimate_is_the_bf16_line_over_the_pixel_area(self, operation): estimate = estimate_vae_working_memory_flux2( operation=operation, image_tensor=self._tensor_for(operation, 1024), vae=self._mock_bf16_vae(), device=FUSED ) - assert estimate.reservation_bytes == 1024 * 1024 * 2 * _vae_k("cudnn", operation) + assert estimate.reservation_bytes == _vae_line("cudnn", operation, 1024 * 1024) def test_tiled_estimate_is_bounded_by_the_tile_not_the_image(self): """Reference-image encoding forces 512px tiling precisely so the peak stops following the @@ -308,7 +309,7 @@ def test_tiled_estimate_is_bounded_by_the_tile_not_the_image(self): for px in (1024, 1328, 2024) ] assert len(set(estimates)) == 1 - assert estimates[0] == int(512 * 512 * 2 * _vae_k("cudnn", "encode") * 1.25) + assert estimates[0] == _vae_line("cudnn", "encode", 512 * 512 * 1.25) # The whole point of tiling: it must shrink the reservation, not just bound the VAE. untiled = estimate_vae_working_memory_flux2( operation="encode", image_tensor=torch.zeros(1, 3, 2024, 2024), vae=self._mock_bf16_vae(), device=FUSED @@ -329,21 +330,21 @@ def test_a_tile_covering_the_image_is_priced_as_the_single_pass_it_runs(self, ti """The VAE tiles only when a side is strictly larger than the tile. Pricing the tile instead reserved 85.9 GiB for a 1024px decode that peaks at 4.3 GiB, evicting the transformer for nothing.""" estimate = self._decode_estimate(1024, 1024, tile_size) - assert estimate.reservation_bytes == 1024 * 1024 * 2 * _vae_k("cudnn", "decode") + assert estimate.reservation_bytes == _vae_line("cudnn", "decode", 1024 * 1024) assert estimate.features["tile"] is None def test_an_elongated_image_prices_the_tile_clamped_per_axis(self): """A 2048px tile on a 1024x4096 image is tiled along the long side only, and every slice it takes is at most 1024x2048.""" estimate = self._decode_estimate(1024, 4096, 2048) - assert estimate.reservation_bytes == int(1024 * 2048 * 2 * _vae_k("cudnn", "decode") * 1.25) + assert estimate.reservation_bytes == _vae_line("cudnn", "decode", 1024 * 2048 * 1.25) @pytest.mark.parametrize("tile_size, resolved", [(0, 512), (64, 128)], ids=["default-sentinel", "below-minimum"]) def test_the_node_tile_field_is_resolved_before_pricing(self, tile_size, resolved): """The nodes pass their `tile_size` field through: 0 means the default tile, and anything under the minimum is raised to it -- pricing the raw value would reserve nothing or too little.""" estimate = self._decode_estimate(1024, 1024, tile_size) - assert estimate.reservation_bytes == int(resolved * resolved * 2 * _vae_k("cudnn", "decode") * 1.25) + assert estimate.reservation_bytes == _vae_line("cudnn", "decode", resolved * resolved * 1.25) assert estimate.features["tile"] == resolved @@ -386,7 +387,8 @@ def estimate(): cuda = estimate() assert rocm > cuda - assert rocm / cuda == pytest.approx(_vae_k("miopen", "decode") / _vae_k("cudnn", "decode"), rel=1e-3) + assert rocm == _vae_line("miopen", "decode", 1024 * 1024) + assert cuda == _vae_line("cudnn", "decode", 1024 * 1024) def test_a_rocm_build_composes_the_miopen_column_with_the_guard_score_matrix(self): """The state every ROCm rig is actually in, driven through the estimator. @@ -412,6 +414,9 @@ def estimate(px): with ( patch("torch.version.hip", "7.1.25424"), + # Expandable segments on Windows, the ROCm default there, where the MIOpen line is the smaller one. + patch("invokeai.backend.working_memory.platform.expandable_segments_enabled", return_value=True), + patch("invokeai.backend.working_memory.platform._host_os", return_value="windows"), patch("invokeai.backend.util.attention._IS_ROCM", True), # Pinned against `INVOKE_ROCM_FUSED_SDPA_MAX_HEAD_DIM`, which a user can set high # enough to disarm the guard; any threshold between the denoise's 128-wide head and @@ -421,10 +426,9 @@ def estimate(px): at_1024 = estimate(1024) at_1536 = estimate(1536) - miopen = _vae_k("miopen", "decode") - assert at_1024 == 1024 * 1024 * 2 * miopen, "at 1024px the MIOpen linear term is the larger" + assert at_1024 == _vae_line("miopen", "decode", 1024 * 1024, "expandable", "windows"), "the line is larger" assert at_1536 == 36864 * 36864 * SDPA_MATH_BYTES_PER_SCORE_ELEMENT, "at 1536px the score matrix is" - assert at_1536 > 1536 * 1536 * 2 * miopen + assert at_1536 > _vae_line("miopen", "decode", 1536 * 1536, "expandable", "windows") class TestFlux2VaeBatchIsBudgeted: @@ -799,7 +803,7 @@ def test_the_score_matrix_of_an_elongated_image_follows_the_clamped_tile(self): device=MATERIALIZING, ).reservation_bytes tokens = 128 * 256 - linear = int(1024 * 2048 * 2 * _vae_k("cudnn", "encode") * 1.25) + linear = _vae_line("cudnn", "encode", 1024 * 2048 * 1.25) assert estimate == max(linear, tokens * tokens * SDPA_MATH_BYTES_PER_SCORE_ELEMENT) @@ -833,7 +837,7 @@ def _estimate(self, px, device, materializing): def test_the_estimate_is_the_larger_term_not_the_sum(self): """At 1024px the two are within 2% of each other on the cuDNN column, which makes this the sharpest place to tell the models apart.""" - linear = 1024 * 1024 * 2 * _vae_k("cudnn", "decode") + linear = _vae_line("cudnn", "decode", 1024 * 1024) score = 16384 * 16384 * SDPA_MATH_BYTES_PER_SCORE_ELEMENT assert self._estimate(1024, MATERIALIZING, materializing=True) == max(linear, score) assert self._estimate(1024, MATERIALIZING, materializing=True) < linear + score @@ -841,7 +845,7 @@ def test_the_estimate_is_the_larger_term_not_the_sum(self): def test_a_fused_build_is_unaffected(self): """The max only ever removes reservation, never adds it: with no score matrix the estimate is the linear term, exactly as before.""" - linear = 1024 * 1024 * 2 * _vae_k("cudnn", "decode") + linear = _vae_line("cudnn", "decode", 1024 * 1024) assert self._estimate(1024, FUSED, materializing=False) == linear def test_regional_prompting_adds_the_score_matrix(self): diff --git a/tests/app/invocations/test_vae_working_memory_backend.py b/tests/app/invocations/test_vae_working_memory_backend.py index 9dfc4fade93..63f8f092335 100644 --- a/tests/app/invocations/test_vae_working_memory_backend.py +++ b/tests/app/invocations/test_vae_working_memory_backend.py @@ -1,9 +1,9 @@ -"""Which convolution-backend coefficients the diffusers-AutoencoderKL estimators use, and what the SD estimator adds up. +"""Which convolution line the diffusers-AutoencoderKL estimators price, and what the SD estimator adds up. -SD1/SDXL, SD3 and CogView4 run the same AutoencoderKL convolution stack as the FLUX.1 autoencoder, so they take the -same measured per-backend coefficients: MIOpen needs ~1.64x what cuDNN does. The change is large enough to matter -- an -SD1.5 1024px fp32 decode reserves 9.49 GB on cuDNN and 15.36 GB on MIOpen -- and the branch is invisible on a CUDA -machine, so it is pinned here rather than left to whichever card the suite runs on. +SD1/SDXL, SD3, FLUX.1 and FLUX.2 share measured lines per operation, dtype and platform (convolution backend, +allocator, and on MIOpen's native allocator the OS); CogView4 carries its own. The selection is invisible on whichever +machine runs the suite -- on the RX 9060 XT an fp16 SDXL decode costs a third of a bf16 one, and the SD nodes run fp16 +whatever dtype the cached VAE holds -- so it is pinned here rather than left to the card. """ from unittest.mock import MagicMock, patch @@ -11,93 +11,355 @@ import pytest import torch from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL +from diffusers.models.autoencoders.autoencoder_kl_flux2 import AutoencoderKLFlux2 +from invokeai.app.invocations.vae.image_to_latents import ImageToLatentsInvocation +from invokeai.app.invocations.vae.latents_to_image import LatentsToImageInvocation from invokeai.backend.util.vae_working_memory import ( VAE_COGVIEW4_SPEC, + VAE_FLUX2_SPEC, + VAE_FLUX_SPEC, VAE_SD3_SPEC, VAE_SD_SPEC, estimate_vae_working_memory_cogview4, + estimate_vae_working_memory_flux, + estimate_vae_working_memory_flux2, estimate_vae_working_memory_sd3, estimate_vae_working_memory_sd15_sdxl, + sd_vae_run_dtype, ) -from invokeai.backend.working_memory.coefficients import shipped_coefficients -from invokeai.backend.working_memory.platform import Platform +from invokeai.backend.working_memory import platform as platform_module +from invokeai.backend.working_memory.coefficients import shipped_coefficients, shipped_data +from invokeai.backend.working_memory.platform import Platform, priced_as +PRE_DTYPE_BYTES_PER_PIXEL_PER_ELEMENT_BYTE = { + "cudnn": {"decode": 2200, "encode": 1100}, + "miopen": {"decode": 3600, "encode": 2750}, +} +"""What these estimators reserved before lines per dtype, per convolution backend, with no base: the floor no stand-in +and nothing unmeasured may price a slope below. Written out rather than read from the data, so a lowered floor fails +here. It covers the convolution lines; the one place v2 can reserve less than v1 there is the SD fp32 upcast, which +v1 guessed at 250 MiB and v2 prices as what the cast adds (160 MiB from fp16, nothing from fp32).""" +ELEMENT_SIZES = {"fp16": 2, "bf16": 2, "fp32": 4} -def _vae(dtype: torch.dtype = torch.float16) -> MagicMock: - vae = MagicMock(spec=AutoencoderKL) - vae.parameters.side_effect = lambda: iter([torch.zeros(1, dtype=dtype)]) +MEASURED = [ + Platform("cudnn", "native", "linux"), + Platform("cudnn", "cudaMallocAsync", "linux"), + Platform("miopen", "native", "linux"), + Platform("miopen", "native", "windows"), + Platform("miopen", "expandable", "windows"), +] + + +def _vae(cls=AutoencoderKL, dtype: torch.dtype = torch.float16, numel: int = 1, params=None) -> MagicMock: + vae = MagicMock(spec=cls) + vae.parameters.side_effect = lambda: iter(params if params is not None else [torch.zeros(numel, dtype=dtype)]) + vae.config.latent_channels = 16 return vae -def _estimate(estimator, device: torch.device, hip: str | None) -> int: - latents = torch.zeros(1, 4, 64, 64) # 512px output +@pytest.fixture +def on(monkeypatch): + """Run the estimate as on `platform`: the torch build, the allocator a CUDA device reports, and the OS.""" + + def pin(platform: Platform) -> torch.device: + monkeypatch.setattr(torch.version, "hip", "7.2.0" if platform.conv_backend == "miopen" else None) + monkeypatch.setattr(torch.cuda, "is_available", lambda: True) + monkeypatch.setattr( + torch.cuda, + "get_allocator_backend", + lambda: "cudaMallocAsync" if platform.allocator == "cudaMallocAsync" else "native", + ) + if platform.allocator == "expandable": + monkeypatch.setenv("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") + monkeypatch.setattr(platform_module, "_host_os", lambda: platform.os) + return torch.device("cuda", 0) + + # The mid-block score matrix is the other term in these estimates and has its own tests; hold it at zero so the + # assertions below are about the convolution line alone. with ( - patch("torch.version.hip", hip), - # The mid-block score matrix is the other term in these estimates and has its own tests; hold it at zero so - # the assertions below are about the convolution coefficient alone. patch("invokeai.backend.util.vae_working_memory._vae_mid_block_score_matrix_bytes", return_value=0), + patch("invokeai.backend.util.vae_working_memory.sdpa_score_matrix_bytes", return_value=0), ): - if estimator is estimate_vae_working_memory_sd15_sdxl: - estimate = estimator( - operation="decode", image_tensor=latents, vae=_vae(), tile_size=None, fp32=False, device=device - ) - else: - estimate = estimator(operation="decode", image_tensor=latents, vae=_vae(), device=device) - return estimate.reservation_bytes + yield pin -def _decode_bytes(spec, conv_backend: str) -> float: - """A 512px fp16 decode at the shipped coefficient of that backend.""" - return 512 * 512 * 2 * shipped_coefficients(spec, Platform(conv_backend)).values["k_decode"] +def _line(spec, platform: Platform, operation: str, dtype: str, pixels: float) -> float: + values = shipped_coefficients(spec, platform).values + return values[f"k_{operation}_{dtype}"] * pixels + values[f"b_{operation}_{dtype}"] -ESTIMATORS = [ - pytest.param(estimate_vae_working_memory_sd15_sdxl, VAE_SD_SPEC, id="sd15-sdxl"), - pytest.param(estimate_vae_working_memory_sd3, VAE_SD3_SPEC, id="sd3"), - pytest.param(estimate_vae_working_memory_cogview4, VAE_COGVIEW4_SPEC, id="cogview4"), -] +LATENTS_1024 = torch.zeros(1, 16, 128, 128) + + +@pytest.mark.parametrize("platform", MEASURED, ids=str) +@pytest.mark.parametrize( + "spec, estimate, vae_dtype, run_dtype", + [ + # The SD nodes cast the VAE to fp16 for every GPU run that does not ask for fp32: bf16 never runs. + pytest.param( + VAE_SD_SPEC, + lambda device, vae: estimate_vae_working_memory_sd15_sdxl("decode", LATENTS_1024, vae, None, False, device), + torch.bfloat16, + "fp16", + id="sd", + ), + pytest.param( + VAE_SD3_SPEC, + lambda device, vae: estimate_vae_working_memory_sd3("decode", LATENTS_1024, vae, device), + torch.float32, + "fp32", + id="sd3", + ), + pytest.param( + VAE_COGVIEW4_SPEC, + lambda device, vae: estimate_vae_working_memory_cogview4("decode", LATENTS_1024, vae, device), + torch.bfloat16, + "bf16", + id="cogview4", + ), + pytest.param( + VAE_FLUX_SPEC, + lambda device, vae: estimate_vae_working_memory_flux("decode", LATENTS_1024, vae, device=device), + torch.bfloat16, + "bf16", + id="flux", + ), + pytest.param( + VAE_FLUX2_SPEC, + lambda device, vae: estimate_vae_working_memory_flux2( + "decode", torch.zeros(1, 32, 128, 128), vae, device=device + ), + torch.float32, + "fp32", + id="flux2", + ), + ], +) +def test_a_decode_reserves_the_line_of_its_run_dtype_on_its_platform( + on, platform, spec, estimate, vae_dtype, run_dtype +) -> None: + cls = AutoencoderKLFlux2 if spec is VAE_FLUX2_SPEC else AutoencoderKL + result = estimate(on(platform), _vae(cls, vae_dtype)) + + assert result.features["dtype"] == run_dtype + assert result.reservation_bytes == int(_line(spec, platform, "decode", run_dtype, 1024 * 1024)) + + +def test_the_platforms_differ_where_the_measurements_do(on) -> None: + """The point of keying on the allocator and, for MIOpen's native allocator, the OS: one SDXL fp16 decode, five + prices.""" + decodes = { + platform: estimate_vae_working_memory_sd15_sdxl( + "decode", LATENTS_1024, _vae(), None, False, on(platform) + ).reservation_bytes + for platform in MEASURED + } + assert decodes[Platform("cudnn", "cudaMallocAsync", "linux")] < decodes[Platform("cudnn", "native", "linux")] + assert decodes[Platform("miopen", "expandable", "windows")] < decodes[Platform("miopen", "native", "windows")] + assert decodes[Platform("miopen", "native", "windows")] != decodes[Platform("miopen", "native", "linux")] + + +def test_the_cogview4_vae_is_priced_on_its_own_lines_without_a_score_matrix(on) -> None: + """Its VAE is wider than the family's and has no mid-block attention: an FLUX.1-sized decode costs ~1.9x.""" + device = on(Platform("cudnn", "native", "linux")) + cogview4 = estimate_vae_working_memory_cogview4("decode", LATENTS_1024, _vae(dtype=torch.bfloat16), device) + flux = estimate_vae_working_memory_flux("decode", LATENTS_1024, _vae(dtype=torch.bfloat16), device=device) + assert "score_matrix_bytes" not in cogview4.features + assert cogview4.reservation_bytes > 1.8 * flux.reservation_bytes + + +@pytest.mark.parametrize( + "operation, fp32, device, run_dtype", + [ + ("decode", False, torch.device("cuda", 0), torch.float16), + ("decode", True, torch.device("cuda", 0), torch.float32), + # The decode node computes in fp32 on the CPU whatever it is asked; the encode node does not. + ("decode", False, torch.device("cpu"), torch.float32), + ("encode", False, torch.device("cpu"), torch.float16), + ("encode", True, torch.device("cuda", 0), torch.float32), + ], +) +@pytest.mark.parametrize("vae_dtype", [torch.float16, torch.bfloat16, torch.float32], ids=str) +def test_the_sd_estimate_prices_the_dtype_the_node_casts_to_not_the_cached_one( + on, operation, fp32, device, run_dtype, vae_dtype +) -> None: + """The cached VAE holds whatever the previous run cast it to; the node casts it again before it runs.""" + on(Platform("cudnn", "native", "linux")) + assert sd_vae_run_dtype(operation, fp32, device) == run_dtype + image = LATENTS_1024[:, :4] if operation == "decode" else torch.zeros(1, 3, 1024, 1024) + result = estimate_vae_working_memory_sd15_sdxl(operation, image, _vae(dtype=vae_dtype), None, fp32, device) + assert result.features["dtype"] == {torch.float16: "fp16", torch.float32: "fp32"}[run_dtype] -@pytest.mark.parametrize("estimator, spec", ESTIMATORS) -def test_a_rocm_vae_reserves_the_miopen_coefficient(estimator, spec): - assert _estimate(estimator, torch.device("cuda", 0), hip="7.2.0") == _decode_bytes(spec, "miopen") +def _tiny_sd_vae() -> AutoencoderKL: + return AutoencoderKL( + down_block_types=("DownEncoderBlock2D",), + up_block_types=("UpDecoderBlock2D",), + block_out_channels=(32,), + layers_per_block=1, + latent_channels=4, + norm_num_groups=32, + ) -@pytest.mark.parametrize("estimator, spec", ESTIMATORS) -def test_a_cuda_vae_reserves_the_cudnn_coefficient(estimator, spec): - assert _estimate(estimator, torch.device("cuda", 0), hip=None) == _decode_bytes(spec, "cudnn") +def _vae_info(vae: AutoencoderKL, estimates: list) -> MagicMock: + def model_on_device(working_memory): + estimates.append(working_memory) + cm = MagicMock() + cm.__enter__.return_value = (None, vae) + cm.__exit__.return_value = None + return cm + vae_info = MagicMock(model=vae, compute_device=torch.device("cpu")) + vae_info.model_on_device.side_effect = model_on_device + return vae_info -@pytest.mark.parametrize("estimator, spec", ESTIMATORS) -def test_a_cpu_only_vae_reserves_the_cudnn_coefficient_even_on_a_rocm_build(estimator, spec): - """A `cpu_only` VAE does not run on MIOpen, so naming its device has to change the answer.""" - assert _estimate(estimator, torch.device("cpu"), hip="7.2.0") == _decode_bytes(spec, "cudnn") +@pytest.mark.parametrize("fp32", [False, True]) +def test_the_decode_node_casts_the_vae_to_the_dtype_its_estimate_priced(fp32) -> None: + """On the CPU both requests decode in fp32: the node and the estimate share `sd_vae_run_dtype`.""" + vae, estimates = _tiny_sd_vae().to(torch.bfloat16), [] + context = MagicMock() + context.tensors.load.return_value = torch.zeros(1, 4, 8, 8) + context.config.get.return_value.force_tiled_decode = False + context.models.load.return_value = _vae_info(vae, estimates) + context.images.save.return_value = MagicMock(image_name="x.png", width=8, height=8) + node = LatentsToImageInvocation.model_construct( + latents=MagicMock(latents_name="x"), vae=MagicMock(seamless_axes=[]), tiled=False, tile_size=0, fp32=fp32 + ) -def _sd_fp32_decode(tile_size: int | None) -> int: - latents = torch.zeros(1, 4, 96, 128) # a 768x1024 output - with patch("invokeai.backend.util.vae_working_memory._vae_mid_block_score_matrix_bytes", return_value=0): - estimate = estimate_vae_working_memory_sd15_sdxl( - operation="decode", - image_tensor=latents, - vae=_vae(), - tile_size=tile_size, - fp32=True, - device=torch.device("cpu"), + node.invoke(context) + + (estimate,) = estimates + assert estimate.features["dtype"] == "fp32" + assert {p.dtype for p in vae.parameters()} == {torch.float32} + + +@pytest.mark.parametrize("upcast, run_dtype", [(False, torch.float16), (True, torch.float32)]) +def test_the_encode_node_casts_the_vae_to_the_dtype_its_estimate_priced(upcast, run_dtype) -> None: + vae, estimates, encoded_in = _tiny_sd_vae().to(torch.bfloat16), [], [] + + def encode(vae, image): + encoded_in.append(next(vae.encoder.parameters()).dtype) + return torch.zeros(1, 4, 2, 2) + + with patch.object(ImageToLatentsInvocation, "_encode_to_tensor", side_effect=encode): + ImageToLatentsInvocation.vae_encode(_vae_info(vae, estimates), upcast, False, torch.zeros(1, 3, 16, 16)) + + (estimate,) = estimates + assert encoded_in == [run_dtype] + assert estimate.features["dtype"] == {torch.float16: "fp16", torch.float32: "fp32"}[run_dtype] + + +@pytest.mark.parametrize( + "vae_dtype, fp32, growth", + [ + (torch.float16, True, 1000 * 2), + (torch.bfloat16, True, 1000 * 2), + (torch.float32, True, 0), + # Casting down frees memory rather than taking it. + (torch.float32, False, 0), + (torch.float16, False, 0), + ], +) +def test_an_sd_upcast_reserves_what_the_cast_adds_to_the_weights(on, vae_dtype, fp32, growth) -> None: + """The node casts the VAE in place once the cache has placed it, so the growth comes out of the reservation.""" + platform = Platform("cudnn", "native", "linux") + device = on(platform) + result = estimate_vae_working_memory_sd15_sdxl( + "decode", LATENTS_1024[:, :4], _vae(dtype=vae_dtype, numel=1000), None, fp32, device + ) + run_dtype = "fp32" if fp32 else "fp16" + assert result.reservation_bytes == int(_line(VAE_SD_SPEC, platform, "decode", run_dtype, 1024 * 1024) + growth) + + +@pytest.mark.parametrize("fp32, run_dtype, growth", [(False, "fp16", 0), (True, "fp32", 1000 * 2)]) +def test_a_tiled_sd_decode_is_one_tile_with_its_margin_the_base_once_and_the_upcast( + on, fp32, run_dtype, growth +) -> None: + platform = Platform("miopen", "expandable", "windows") + result = estimate_vae_working_memory_sd15_sdxl( + "decode", torch.zeros(1, 4, 192, 192), _vae(numel=1000), 512, fp32, on(platform) + ) + assert result.reservation_bytes == int(_line(VAE_SD_SPEC, platform, "decode", run_dtype, 512 * 512 * 1.25) + growth) + + +def test_a_dtype_the_estimator_has_no_line_for_is_refused_by_name(on) -> None: + """The FLUX.2 loaders never run fp16; a VAE that does anyway must not fail mid-generation on a bare KeyError.""" + device = on(Platform("cudnn", "native", "linux")) + with pytest.raises(ValueError, match=r"'vae\.flux2' has no fp16 decode line on Platform\(conv_backend='cudnn'"): + estimate_vae_working_memory_flux2( + "decode", torch.zeros(1, 32, 64, 64), _vae(AutoencoderKLFlux2, torch.float16), device=device ) - return estimate.reservation_bytes -FP32_WEIGHTS = 250 * 2**20 -"""What an fp32 decode adds for the upcast weights.""" +@pytest.mark.parametrize( + "leading, dtype", + [ + pytest.param(torch.zeros(4, dtype=torch.float8_e4m3fn), "bf16", id="fp8-storage"), + pytest.param(torch.zeros(4, dtype=torch.int8), "bf16", id="packed-integer"), + ], +) +def test_the_compute_dtype_skips_weights_stored_narrower_than_they_compute(on, leading, dtype) -> None: + """An SDNQ VAE's first parameter can be its packed integer storage; it computes in its floating-point layers'.""" + device = on(Platform("cudnn", "native", "linux")) + vae = _vae(params=[leading, torch.zeros(1, dtype=torch.bfloat16)]) + assert estimate_vae_working_memory_flux("decode", LATENTS_1024, vae, device=device).features["dtype"] == dtype + +def test_every_stand_in_slope_is_at_least_the_pre_dtype_figure() -> None: + """A stand-in is a declared conservative value on a measured platform; nothing else guards it.""" + stand_ins = [entry for data in shipped_data().values() for entry in data.coefficients if entry.stand_ins] + assert stand_ins, "the shipped data declares no stand-ins -- the check is broken" + for entry in stand_ins: + for name in entry.stand_ins: + part, operation, dtype = name.split("_") + value = entry.values[name] + if part == "k": + floor = PRE_DTYPE_BYTES_PER_PIXEL_PER_ELEMENT_BYTE[entry.platform["conv_backend"]][operation] + assert value >= floor * ELEMENT_SIZES[dtype], f"{entry.estimator} {entry.platform} {name}" + else: + assert value >= 0 -def test_a_tiled_fp32_sd_decode_is_one_tile_with_its_margin_plus_the_fp32_weights(): - k = shipped_coefficients(VAE_SD_SPEC, Platform("cudnn")).values["k_decode"] - assert _sd_fp32_decode(512) == int(512 * 512 * 4 * k * 1.25 + FP32_WEIGHTS) + +@pytest.mark.parametrize( + "spec", [VAE_SD_SPEC, VAE_SD3_SPEC, VAE_COGVIEW4_SPEC, VAE_FLUX_SPEC, VAE_FLUX2_SPEC], ids=lambda spec: spec.id +) +@pytest.mark.parametrize( + "platform", + [ + Platform("mps", "native", "other"), + Platform("xpu", "native", "windows"), + Platform("cpu", "native", "linux"), + Platform("cudnn", "expandable", "linux"), + Platform("miopen", "expandable", "linux"), + Platform("miopen", "expandable", "other"), + Platform("miopen", "cudaMallocAsync", "linux"), + Platform("miopen", "cudaMallocAsync", "windows"), + ], + ids=str, +) +def test_an_unmeasured_platform_never_prices_a_slope_below_the_pre_dtype_figure(spec, platform) -> None: + measured = priced_as(platform) + values = shipped_coefficients(spec, platform).values + for name, value in values.items(): + part, operation, dtype = name.split("_") + if part == "k": + floor = PRE_DTYPE_BYTES_PER_PIXEL_PER_ELEMENT_BYTE[measured.conv_backend][operation] * ELEMENT_SIZES[dtype] + assert value >= floor, f"{name} on {platform}: {value} under {floor}" + else: + assert value >= 0 -def test_an_untiled_fp32_sd_decode_is_the_whole_image_plus_the_fp32_weights(): - k = shipped_coefficients(VAE_SD_SPEC, Platform("cudnn")).values["k_decode"] - assert _sd_fp32_decode(None) == int(768 * 1024 * 4 * k + FP32_WEIGHTS) +def test_expandable_segments_off_windows_are_priced_as_the_native_allocator_there(on) -> None: + """Measured on Windows only, where they are Invoke's ROCm default. On Linux they are a user override nobody + measured: an SDXL fp16 decode there must not take the Windows 2.5 GiB at 1024px, nor go under the pre-dtype 7200 MiB.""" + linux = estimate_vae_working_memory_sd15_sdxl( + "decode", LATENTS_1024[:, :4], _vae(), None, False, on(Platform("miopen", "expandable", "linux")) + ) + native = estimate_vae_working_memory_sd15_sdxl( + "decode", LATENTS_1024[:, :4], _vae(), None, False, on(Platform("miopen", "native", "linux")) + ) + assert linux.reservation_bytes == native.reservation_bytes >= 3600 * 2 * 1024 * 1024 + assert "priced as conv_backend=miopen,allocator=native" in linux.source diff --git a/tests/app/invocations/test_z_image_tiled_decode.py b/tests/app/invocations/test_z_image_tiled_decode.py index eb9d3a3a9b6..df6167ae418 100644 --- a/tests/app/invocations/test_z_image_tiled_decode.py +++ b/tests/app/invocations/test_z_image_tiled_decode.py @@ -20,19 +20,23 @@ def _flux_reservation(*args, **kwargs) -> int: return estimate_vae_working_memory_flux(*args, **kwargs).reservation_bytes -def _k(conv_backend: str, operation: str) -> float: - """The shipped FLUX.1 VAE coefficient the estimator resolves for one convolution backend and operation.""" - return shipped_coefficients(VAE_FLUX_SPEC, Platform(conv_backend)).values[f"k_{operation}"] +CPU = Platform("cpu", "native", "linux") + + +def _line(platform: Platform, operation: str, pixels: float, dtype: str = "bf16") -> float: + """What the shipped FLUX.1 VAE line the estimator resolves on `platform` prices over `pixels` output pixels.""" + values = shipped_coefficients(VAE_FLUX_SPEC, platform).values + return values[f"k_{operation}_{dtype}"] * pixels + values[f"b_{operation}_{dtype}"] def _mock_flux_vae(element_size_bytes: int = 2) -> MagicMock: - """A stand-in for the estimator cells only: they read nothing but the parameter element size. + """A stand-in for the estimator cells only: they read nothing but the parameter dtype. The node-wiring cells below use a real `AutoencoderKL`, because they are about tiling state a mock cannot have -- `tile_overlap_factor` is set in `__init__`, so a spec'd mock lacks it. """ vae = MagicMock(spec=AutoencoderKL) - dtype = torch.float16 if element_size_bytes == 2 else torch.float32 + dtype = torch.bfloat16 if element_size_bytes == 2 else torch.float32 # A fresh iterator per call: the decode path reads `parameters()` after the estimator already # has, and a single stored iterator would be exhausted by then. vae.parameters.side_effect = lambda: iter([torch.zeros(1, dtype=dtype)]) @@ -85,14 +89,14 @@ def test_the_convolution_backend_of_the_vaes_device_picks_the_constant( operation=operation, image_tensor=tensor, vae=_mock_flux_vae(), device=torch.device("cpu") ) - assert estimate == 512 * 512 * 2 * _k(conv_backend, operation) - assert on_cpu == 512 * 512 * 2 * _k("cudnn", operation), "a cpu_only VAE runs on cuDNN terms" + assert estimate == int(_line(Platform(conv_backend, "native", "linux"), operation, 512 * 512)) + assert on_cpu == int(_line(CPU, operation, 512 * 512)), "a cpu_only VAE runs on cuDNN terms" def test_the_default_reproduces_the_untiled_estimate(self): """Regression guard for the six call sites that pass no tile_size at all.""" latents = torch.zeros(1, 16, 128, 128) - # 1024x1024 output px * 2 bytes * the cuDNN decode coefficient - expected = 1024 * 1024 * 2 * _k("cudnn", "decode") + # The bf16 decode line over 1024x1024 output pixels + expected = int(_line(CPU, "decode", 1024 * 1024)) actual = _flux_reservation(operation="decode", image_tensor=latents, vae=_mock_flux_vae()) assert actual == expected @@ -104,8 +108,8 @@ def test_the_tiled_estimate_is_a_tile_bounded_decode_plus_the_assembled_image(se margin or halving the per-pixel image cost would be a several-hundred-megabyte under-reservation that no other assertion here can see. """ - # 512px tiles, fp16, over a 1536x1536 output. - decode_term = 512 * 512 * 2 * _k("cudnn", "decode") * 1.25 + # 512px tiles, bf16, over a 1536x1536 output. + decode_term = _line(CPU, "decode", 512 * 512 * 1.25) # 4 RGB buffers at the VAE's element size, plus the 8-bit one the node hands to PIL. Four # is measured, not assumed: the assembled image grows the device peak by ~1.6 copies on a # diffusers VAE and ~0.1 on the host-merging BFL one, and the node adds its in-window @@ -116,17 +120,14 @@ def test_the_tiled_estimate_is_a_tile_bounded_decode_plus_the_assembled_image(se ) assert estimate == int(decode_term + image_term) - def test_both_tiled_terms_scale_with_the_vaes_element_size(self): - """An fp32 VAE costs twice the bytes per pixel, in the tile and in the assembled image - alike. Only the 8-bit buffer is fixed, which is why this is a little under 2x.""" + def test_both_tiled_terms_follow_the_vaes_dtype(self): + """An fp32 VAE prices its tile at the fp32 line, which is not twice the bf16 one, and its assembled image at + four bytes per value. Only the 8-bit buffer is fixed.""" latents = torch.zeros(1, 16, 192, 192) - fp16 = _flux_reservation( - operation="decode", image_tensor=latents, vae=_mock_flux_vae(element_size_bytes=2), tile_size=512 - ) fp32 = _flux_reservation( operation="decode", image_tensor=latents, vae=_mock_flux_vae(element_size_bytes=4), tile_size=512 ) - assert 1.95 < fp32 / fp16 < 2.0 + assert fp32 == int(_line(CPU, "decode", 512 * 512 * 1.25, "fp32") + 1536 * 1536 * 3 * (4 * 4 + 1)) @pytest.mark.parametrize("latent_hw", [(128, 128), (192, 192), (256, 256)]) def test_a_tiled_estimate_stays_a_fraction_of_the_untiled_one(self, latent_hw): @@ -179,7 +180,7 @@ def test_a_tiled_estimate_still_covers_the_assembled_image(self): estimate = _flux_reservation( operation="decode", image_tensor=torch.zeros(1, 16, 1024, 1024), vae=_mock_flux_vae(), tile_size=512 ) - tile_term = int(512 * 512 * 2 * _k("cudnn", "decode") * 1.25) + tile_term = int(_line(CPU, "decode", 512 * 512 * 1.25)) one_full_resolution_buffer = 8192 * 8192 * 3 * 2 # The assembled output plus at least one of the post-processing copies. assert estimate >= tile_term + 2 * one_full_resolution_buffer @@ -201,7 +202,7 @@ def test_the_sentinel_works_on_a_vae_without_that_attribute_at_all(self): # sibling dereferences `vae.tile_sample_min_size` directly and would raise here. latents = torch.zeros(1, 16, 192, 192) vae = MagicMock(spec=AutoencoderKL) - vae.parameters.side_effect = lambda: iter([torch.zeros(1, dtype=torch.float16)]) + vae.parameters.side_effect = lambda: iter([torch.zeros(1, dtype=torch.bfloat16)]) del vae.tile_sample_min_size estimate = _flux_reservation(operation="decode", image_tensor=latents, vae=vae, tile_size=0) assert estimate == _flux_reservation( @@ -235,7 +236,7 @@ def _fused_non_rocm_build(self, monkeypatch): def test_an_untiled_encode_is_the_whole_image_at_the_encode_coefficient(self): """What the call sites that pass no tile reserve: the full image, priced at the cuDNN encode coefficient.""" image = torch.zeros(1, 3, 1024, 1024) - assert _flux_reservation("encode", image, _mock_flux_vae()) == 1024 * 1024 * 2 * _k("cudnn", "encode") + assert _flux_reservation("encode", image, _mock_flux_vae()) == int(_line(CPU, "encode", 1024 * 1024)) def test_a_tiled_estimate_is_flat_in_the_image_size_where_the_untiled_one_is_not(self): vae = _mock_flux_vae() @@ -247,7 +248,7 @@ def test_a_tiled_estimate_is_flat_in_the_image_size_where_the_untiled_one_is_not # But they are a rounding error beside the tile term, which is what "flat" has to mean. assert max(estimates) < min(estimates) * 1.2 untiled = [_flux_reservation("encode", torch.zeros(1, 3, edge, edge), vae) for edge in (1024, 3072)] - assert untiled[1] == untiled[0] * 9 + assert untiled == [int(_line(CPU, "encode", edge * edge)) for edge in (1024, 3072)] def test_the_tiled_estimate_is_one_tile_plus_the_input_and_every_live_copy_of_the_moments(self): """The moments are not one copy. `AutoencoderKL._tiled_encode` keeps every encoded tile in @@ -256,7 +257,7 @@ def test_the_tiled_estimate_is_one_tile_plus_the_input_and_every_live_copy_of_th vae = _mock_flux_vae() edge, tile = 2048, 512 live_moment_copies = 1.0 / (1.0 - 0.25) ** 2 + 2.0 - expected = tile * tile * 2 * _k("cudnn", "encode") * 1.25 + expected = _line(CPU, "encode", tile * tile * 1.25) expected += edge * edge * 3 * 2 expected += (edge // 8) * (edge // 8) * 2 * 16 * 2 * live_moment_copies assert _flux_reservation("encode", torch.zeros(1, 3, edge, edge), vae, tile_size=tile) == int(expected) diff --git a/tests/backend/model_manager/load/model_cache/test_model_cache_vram_accounting.py b/tests/backend/model_manager/load/model_cache/test_model_cache_vram_accounting.py index a5f7f41ec81..da28b8c3736 100644 --- a/tests/backend/model_manager/load/model_cache/test_model_cache_vram_accounting.py +++ b/tests/backend/model_manager/load/model_cache/test_model_cache_vram_accounting.py @@ -323,7 +323,7 @@ def _release(self, monkeypatch, *, driver_free_mb: int, releasable_mb: int, work TorchDevice, "cuda_mem_get_info", classmethod(lambda cls, d: (driver_free_mb * MB, 24 * GB)) ) monkeypatch.setattr(cache, "_get_reclaimable_allocator_bytes", lambda: releasable_mb * MB) - monkeypatch.setattr(model_cache_module, "_expandable_segments_enabled", lambda: False) + monkeypatch.setattr(model_cache_module, "expandable_segments_enabled", lambda: False) monkeypatch.setattr(TorchDevice, "empty_cache", classmethod(lambda cls, force=False: calls.append(force))) cache._release_allocator_blocks_for_reserve(None if working_mb is None else working_mb * MB) return calls @@ -359,7 +359,7 @@ def test_under_expandable_segments_the_unused_reserved_pages_count(self, monkeyp cache = self._cache("cuda") calls: list = [] monkeypatch.setattr(TorchDevice, "cuda_mem_get_info", classmethod(lambda cls, d: (2 * GB, 24 * GB))) - monkeypatch.setattr(model_cache_module, "_expandable_segments_enabled", lambda: True) + monkeypatch.setattr(model_cache_module, "expandable_segments_enabled", lambda: True) monkeypatch.setattr(cache, "_get_reclaimable_allocator_bytes", lambda: 0) monkeypatch.setattr(torch.cuda, "memory_reserved", lambda device=None: 9 * GB) monkeypatch.setattr(torch.cuda, "memory_allocated", lambda device=None: 6 * GB) diff --git a/tests/backend/util/test_rocm_sdpa_head_dim_guard.py b/tests/backend/util/test_rocm_sdpa_head_dim_guard.py index f5571889c05..80df996e5f7 100644 --- a/tests/backend/util/test_rocm_sdpa_head_dim_guard.py +++ b/tests/backend/util/test_rocm_sdpa_head_dim_guard.py @@ -499,6 +499,8 @@ def _rocm_cuda(self, monkeypatch): monkeypatch.setattr(attention, "_IS_ROCM", True) monkeypatch.setattr(torch.version, "hip", None) monkeypatch.setattr(vwm.TorchDevice, "choose_torch_device", classmethod(lambda cls: torch.device("cuda", 0))) + # The native allocator, whatever the machine running the suite configures. + monkeypatch.setattr("invokeai.backend.working_memory.platform._allocator_for", lambda device: "native") @staticmethod def _vae(spec): @@ -514,13 +516,14 @@ def _score_bytes(px: int) -> int: return tokens * tokens * attention.SDPA_MATH_BYTES_PER_SCORE_ELEMENT @staticmethod - def _cudnn_decode_bytes(px: int) -> float: - """A bf16 decode's convolution term at the shipped cuDNN coefficient.""" + def _cudnn_decode_bytes(px: int) -> int: + """An SD fp16 decode's convolution line at the shipped cuDNN coefficients: the GPU run casts the VAE to fp16.""" from invokeai.backend.util.vae_working_memory import VAE_SD_SPEC from invokeai.backend.working_memory.coefficients import shipped_coefficients from invokeai.backend.working_memory.platform import Platform - return px * px * 2 * shipped_coefficients(VAE_SD_SPEC, Platform("cudnn")).values["k_decode"] + values = shipped_coefficients(VAE_SD_SPEC, Platform("cudnn", "native", "linux")).values + return int(values["k_decode_fp16"] * px * px + values["b_decode_fp16"]) def test_sdxl_untiled_decode_reserves_the_score_matrix_when_it_dominates(self, monkeypatch): from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL @@ -547,16 +550,25 @@ def test_sdxl_untiled_decode_reserves_the_score_matrix_when_it_dominates(self, m == linear ) - @pytest.mark.parametrize("estimator", ["estimate_vae_working_memory_sd3", "estimate_vae_working_memory_cogview4"]) - def test_the_other_autoencoder_kl_estimators_price_it_too(self, estimator): + def test_the_sd3_estimator_prices_it_too(self): from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL - import invokeai.backend.util.vae_working_memory as vwm + from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd3 latents = torch.zeros(1, 16, 2048 // 8, 2048 // 8) - estimate = getattr(vwm, estimator)("decode", latents, self._vae(AutoencoderKL)) + estimate = estimate_vae_working_memory_sd3("decode", latents, self._vae(AutoencoderKL)) assert estimate.reservation_bytes == self._score_bytes(2048) + def test_the_cogview4_vae_has_no_attention_to_price(self): + """Its config sets `mid_block_add_attention: false`: the guard never sees it.""" + from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL + + from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_cogview4 + + latents = torch.zeros(1, 16, 2048 // 8, 2048 // 8) + estimate = estimate_vae_working_memory_cogview4("decode", latents, self._vae(AutoencoderKL)) + assert all(term.coefficient is not None for term in estimate.terms) + def test_a_tiny_autoencoder_has_no_attention_to_price(self): from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny diff --git a/tests/backend/working_memory/test_fit.py b/tests/backend/working_memory/test_fit.py new file mode 100644 index 00000000000..005c3614fbe --- /dev/null +++ b/tests/backend/working_memory/test_fit.py @@ -0,0 +1,143 @@ +"""The shipped fit: an upper envelope of the points, with a non-negative base, plus the shipped margin.""" + +from fractions import Fraction + +import pytest + +from invokeai.backend.working_memory.coefficients import CoefficientEntry, MeasuredPoint, ValuesFrom, WorkingMemoryData +from invokeai.backend.working_memory.fit import SHIPPED_MARGIN, fitted_values, shipped_pair, upper_envelope + +MIB = 2**20 + +# A process base plus a per-pixel cost, read with the scatter real points have: the shape the VAE points take. +SCATTERED = [(262144, 940 * MIB), (589824, 1540 * MIB), (1048576, 2380 * MIB), (1638400, 3120 * MIB)] +PROPORTIONAL = [(262144, 1100 * MIB), (589824, 2457 * MIB), (1048576, 4330 * MIB), (4194304, 17194 * MIB)] + + +@pytest.mark.parametrize("points", [SCATTERED, PROPORTIONAL], ids=["with-a-base", "proportional"]) +def test_the_envelope_covers_every_point_with_a_non_negative_base(points) -> None: + k, b = upper_envelope(points) + assert b >= 0 + assert all(k * x + b >= y for x, y in points) + + +def test_the_envelope_is_the_hull_edge_that_over_reserves_its_worst_point_least() -> None: + """Through the origin and the 512px point the line covers everything too, but over-reserves 1280px by 1.48x; the + edge from 512px to 1024px over-reserves its worst point, 1280px, by 1.11x.""" + k, b = upper_envelope(SCATTERED) + assert (k, b) == (Fraction(600 * MIB, 327680), 940 * MIB - Fraction(600 * MIB, 327680) * 262144) + assert max((k * x + b) / y for x, y in SCATTERED) < Fraction(112, 100) + + +def test_the_largest_reading_of_a_size_is_the_one_covered() -> None: + k, b = upper_envelope([(262144, 1000 * MIB), (262144, 1100 * MIB), (1048576, 4400 * MIB)]) + assert k * 262144 + b >= 1100 * MIB + + +def test_a_tie_between_two_edges_goes_to_the_steeper_one() -> None: + """Both outer edges over-reserve their worst point by 1.25x; the steeper is the more conservative past the largest + point.""" + assert upper_envelope([(1, 10), (2, 15), (4, 20)]) == (5, 5) + + +def test_a_lower_reading_at_a_measured_size_does_not_move_the_line() -> None: + """Rated against every reading, the 12 at size 4 would hand the line to the flatter edge (it over-reserves 12 by + 1.67x instead of 2.08x) though no point lies above the line the highest readings give.""" + assert upper_envelope([(1, 10), (2, 15), (4, 20), (4, 12)]) == upper_envelope([(1, 10), (2, 15), (4, 20)]) + + +def test_a_falling_edge_is_never_the_line() -> None: + """A line that falls with the size would price larger images below smaller ones.""" + assert upper_envelope([(1, 10), (4, 5)]) == (10, 0) + + +def test_the_shipped_margin_is_five_percent() -> None: + assert SHIPPED_MARGIN == Fraction(5, 100) + + +def test_the_shipped_pair_adds_the_margin_and_rounds_up() -> None: + k, b = upper_envelope(SCATTERED) + shipped_k, shipped_b = shipped_pair(SCATTERED) + assert shipped_k >= k * (1 + SHIPPED_MARGIN) > shipped_k - 1 + assert shipped_b % MIB == 0 + assert shipped_b >= b * (1 + SHIPPED_MARGIN) > shipped_b - MIB + + +@pytest.mark.parametrize( + "points", + [ + pytest.param([], id="none"), + pytest.param([(1048576, 4330 * MIB)], id="one-size"), + pytest.param([(1048576, 4330 * MIB), (1048576, 4300 * MIB)], id="one-size-twice"), + pytest.param([(1048576, 4330 * MIB), (1638400, 6748 * MIB)], id="sizes-too-close"), + ], +) +def test_a_line_needs_two_sizes_at_least_twice_apart(points) -> None: + with pytest.raises(ValueError): + upper_envelope(points) + + +def _point(estimator: str, operation: str, px: int, mib: int, **extra) -> MeasuredPoint: + features = {"operation": operation, "dtype": "bf16", "height": px, "width": px, "tile": None} | extra + return MeasuredPoint( + estimator=estimator, + version=1, + platform={"conv_backend": "cudnn"}, + features=features, + measured_bytes=mib * MIB, + method="reserved_peak", + device="-", + provenance="-", + ) + + +def _entry(estimator: str, values=None, lender: str | None = None) -> CoefficientEntry: + return CoefficientEntry( + estimator=estimator, + version=1, + platform={"conv_backend": "cudnn"}, + values=values, + values_from=ValuesFrom(estimator=lender, version=1) if lender else None, + provenance="-", + ) + + +def test_a_line_is_fitted_on_the_single_sample_untiled_points_of_every_member_that_borrows_it() -> None: + values = {"k_decode_bf16": 0.0, "b_decode_bf16": 0.0} + data = WorkingMemoryData( + schema_version=1, + coefficients=[_entry("lender", values), _entry("borrower", lender="lender")], + points=[ + _point("lender", "decode", 512, 1100), + _point("borrower", "decode", 1024, 4400), + # None of these is the line's: another operation, a tile, a batch, a point out of regime, and a point whose + # demand includes an in-place upcast the estimate prices as a term of its own. + _point("borrower", "encode", 1024, 9000), + _point("borrower", "decode", 2048, 1000, tile=512), + _point("borrower", "decode", 1024, 9000, batch_size=2), + _point("borrower", "decode", 2048, 90000, upcast_weight_bytes=160 * MIB), + MeasuredPoint.model_validate(_point("lender", "decode", 2048, 90000).model_dump() | {"in_fit": False}), + ], + ) + fitted = fitted_values([data])[("lender", 1, (("conv_backend", "cudnn"),))] + assert (fitted["k_decode_bf16"], fitted["b_decode_bf16"]) == shipped_pair( + [(512 * 512, 1100 * MIB), (1024 * 1024, 4400 * MIB)] + ) + + +def test_a_line_without_enough_points_names_its_entry() -> None: + data = WorkingMemoryData( + schema_version=1, + coefficients=[_entry("lender", {"k_decode_bf16": 0.0, "b_decode_bf16": 0.0})], + points=[_point("lender", "decode", 512, 1100)], + ) + with pytest.raises(ValueError, match=r"lender on \{'conv_backend': 'cudnn'\}, decode_bf16"): + fitted_values([data]) + + +def test_a_declared_stand_in_line_is_not_fitted() -> None: + entry = _entry("lender", {"k_decode_bf16": 7200.0, "b_decode_bf16": 0.0}).model_copy( + update={"stand_ins": ["k_decode_bf16", "b_decode_bf16"]} + ) + data = WorkingMemoryData(schema_version=1, coefficients=[entry], points=[]) + assert fitted_values([data]) == {} diff --git a/tests/backend/working_memory/test_shipped_coefficients.py b/tests/backend/working_memory/test_shipped_coefficients.py index d369d03df90..829a3386083 100644 --- a/tests/backend/working_memory/test_shipped_coefficients.py +++ b/tests/backend/working_memory/test_shipped_coefficients.py @@ -1,17 +1,20 @@ """The shipped working-memory data, checked against the estimators that read it. -Every coefficient entry resolves for the spec it names on a measured platform value, and every value an entry carries -is decided by at least one measured point. A point is peak *reserved* bytes, one fresh process per point (see its -`provenance`), and bounds its estimate from both sides. The model cache consumes an estimate as `free >= estimate`, so -the estimate must be at least the point. Where a fitted term decides it, it must also be at most 1.3 times the point, -or the point's tighter `max_ratio`: a reservation the operation never uses is model weights evicted for nothing. Where -an analytic term (a materialized score matrix) decides it, that term bounds rather than describes, so it gets no upper -bound, and the cache's `device_working_mem_gb` floor counts toward the lower one. +Every coefficient entry resolves for the spec it names on a measured platform. Every value an entry carries is either a +declared stand-in or decided by at least one in-fit point and the shipped fit (`fit.py`) of the points it is resolved +for. A point is a peak over the operation, one fresh process per point (see its `method` and `provenance`), and bounds +its estimate from both sides. The model cache consumes an estimate as `free >= estimate`, so the estimate must be at +least the point. Where a fitted term decides an in-fit point, it must also be at most 1.3 times the point, or the +point's tighter `max_ratio`: a reservation the operation never uses is model weights evicted for nothing. The points no +line can hold that close are listed in `UPPER_BOUND_EXCEPTIONS`. Where an analytic term (a materialized score matrix) +decides it, that term bounds rather than describes, so it gets no upper bound, and the cache's `device_working_mem_gb` +floor counts toward the lower one. An out-of-fit point gets the lower bound only, and `OUT_OF_FIT` lists them. """ +import collections import itertools import re -from dataclasses import asdict +from dataclasses import replace from pathlib import Path from typing import get_args, get_type_hints @@ -21,6 +24,8 @@ import invokeai from invokeai.app.services.config.config_default import InvokeAIAppConfig +from invokeai.app.util.torch_cuda_allocator import ROCM_WINDOWS_ALLOC_CONF +from invokeai.backend.working_memory import platform as platform_module from invokeai.backend.working_memory.coefficients import ( CoefficientEntry, MeasuredPoint, @@ -33,7 +38,8 @@ shipped_data, ) from invokeai.backend.working_memory.estimate import WorkingMemoryEstimate -from invokeai.backend.working_memory.platform import UNMEASURED_PRICED_AS, Platform, platform_for +from invokeai.backend.working_memory.fit import carrier_of, entry_key, fit_points, fitted_values +from invokeai.backend.working_memory.platform import MEASURED, Platform, is_measured, platform_for, priced_as from invokeai.backend.working_memory.spec import ESTIMATOR_MODULES, EstimatorSpec, import_estimator_modules MAX_OVER_RESERVATION = 1.3 @@ -44,26 +50,41 @@ """What `ModelCache._working_mem_reserve` floors every reservation at. The field default, not a config instance an environment variable could move.""" +UPPER_BOUND_EXCEPTIONS = { + "vae.sd-miopen,native,windows-decode-fp16-1280x1280-RX9060XT-demand_peak": 1.33, + "vae.sd-miopen,native,windows-encode-fp16-1024x1024-RX9060XT-demand_peak": 1.57, + "vae.sd-miopen,native,windows-encode-fp16-1280x1280-RX9060XT-demand_peak": 1.78, + "vae.flux-miopen,native,windows-decode-bf16-512x512-RX9060XT-demand_peak": 1.33, + "vae.flux2-miopen,native,windows-decode-bf16-512x512-RX9060XT-demand_peak": 1.33, +} +"""In-fit points whose envelope sits more than 1.3x above them, with the ratio they are held to instead. All are MIOpen +with the native allocator on Windows, where the demand is concave in the size: first-run driver spikes at small sizes +and a second run reserving up to 1.6x the first at large ones, so a line that covers every point cannot stay within +1.3x of all of them. Windows ROCm defaults to expandable segments; this is the override's price.""" + +OUT_OF_FIT = {((("allocator", "native"), ("conv_backend", "cudnn")), "RTX 4090", "reserved_peak"): 6} +"""The out-of-fit points by platform, device and method: the six CUDA forced-math-kernel decodes and encodes, which +check the score-matrix term. A point that leaves the fit needs an edit here.""" + # Imported deliberately rather than relying on whichever modules ran first: a spec missing from the registry is a spec # whose data nobody checks. SPECS = import_estimator_modules() +EVERY_PLATFORM = [ + Platform(**dict(zip(get_type_hints(Platform), values, strict=True))) + for values in itertools.product(*(get_args(hint) for hint in get_type_hints(Platform).values())) +] +MEASURED_PLATFORMS = [platform for platform in EVERY_PLATFORM if is_measured(platform)] +UNMEASURED_PLATFORMS = [platform for platform in EVERY_PLATFORM if not is_measured(platform)] -def _platforms(*, measured: bool) -> list[Platform]: - """Every platform built from measured values only, or every platform with at least one unmeasured value.""" - every = {key: get_args(hint) for key, hint in get_type_hints(Platform).items()} - measured_values = { - key: [v for v in values if v not in UNMEASURED_PRICED_AS.get(key, {})] for key, values in every.items() - } - chosen = measured_values if measured else every - platforms = [Platform(**dict(zip(chosen, values, strict=True))) for values in itertools.product(*chosen.values())] - if measured: - return platforms - return [p for p in platforms if any(value in UNMEASURED_PRICED_AS.get(key, {}) for key, value in asdict(p).items())] +def _keys(platform: Platform, spec: EstimatorSpec) -> dict[str, str]: + return {key: getattr(platform, key) for key in spec.platform_keys} -def _platform_key(platform: dict[str, str]) -> tuple[tuple[str, str], ...]: - return tuple(sorted(platform.items())) + +def _platform_of(point: MeasuredPoint) -> Platform: + """The platform a point is evaluated on. One that names no OS measured what holds on every OS.""" + return Platform(**({"os": "other"} | point.platform)) def _point_id(point: MeasuredPoint) -> str: @@ -71,23 +92,28 @@ def _point_id(point: MeasuredPoint) -> str: shape = f"{features.get('height')}x{features.get('width')}" tile = f"-tile{features['tile']}" if features.get("tile") is not None else "" batch = f"-b{features['batch_size']}" if features.get("batch_size", 1) != 1 else "" - scores = "-scores" if features.get("score_matrix_bytes") else "" + out_of_fit = "-outfit" if not point.in_fit else "" platform = ",".join(point.platform.values()) card = point.device.split(" (")[0].replace(" ", "") - return f"{point.estimator}-{platform}-{features.get('operation')}-{shape}{tile}{batch}{scores}-{card}" + return ( + f"{point.estimator}-{platform}-{features.get('operation')}-{features.get('dtype')}-{shape}{tile}{batch}" + f"{out_of_fit}-{card}-{point.method}" + ) def _evaluate(point: MeasuredPoint) -> WorkingMemoryEstimate: spec = SPECS[point.estimator] assert point.version == spec.version - return spec.evaluate(point.features, shipped_coefficients(spec, Platform(**point.platform))) + return spec.evaluate(point.features, shipped_coefficients(spec, _platform_of(point))) def _fitted_decides(estimate: WorkingMemoryEstimate) -> bool: return any(term.included and term.coefficient is not None for term in estimate.terms) -POINTS = [pytest.param(point, id=_point_id(point)) for data in shipped_data().values() for point in data.points] +ALL_POINTS = [point for data in shipped_data().values() for point in data.points] +POINTS = [pytest.param(point, id=_point_id(point)) for point in ALL_POINTS] +ENTRIES = [entry for data in shipped_data().values() for entry in data.coefficients] def test_every_data_file_parses_and_declares_coefficients() -> None: @@ -95,70 +121,128 @@ def test_every_data_file_parses_and_declares_coefficients() -> None: assert data.coefficients, f"{name} declares no coefficients" -def test_every_coefficient_entry_resolves_for_its_spec_on_a_measured_platform_value() -> None: +def test_every_coefficient_entry_resolves_for_its_spec_on_a_measured_platform() -> None: """Resolving checks what an entry provides against what its spec reads, the borrowed values included. An entry for - an unmeasured value would never be read: the resolver prices that value as `UNMEASURED_PRICED_AS` says.""" + an unmeasured platform would never be read: the resolver prices that platform as `priced_as` says. Only the OS may + refine the spec's keys.""" seen: set[tuple[str, int, tuple[tuple[str, str], ...]]] = set() - for name, data in shipped_data().items(): - for entry in data.coefficients: - where = f"{name}: {entry.estimator} on {entry.platform}" - spec = SPECS.get(entry.estimator) - assert spec is not None, f"{where}: no estimator registers this id" - assert entry.version == spec.version, f"{where}: version {entry.version}, the estimator is {spec.version}" - assert set(entry.platform) == set(spec.platform_keys), f"{where}: the spec keys {spec.platform_keys}" - for key, value in entry.platform.items(): - assert value not in UNMEASURED_PRICED_AS.get(key, {}), f"{where}: {key}={value} is not measured" - key = (entry.estimator, entry.version, _platform_key(entry.platform)) - assert key not in seen, f"{where}: declared twice" - seen.add(key) - shipped_coefficients(spec, Platform(**entry.platform)) + for entry in ENTRIES: + where = f"{entry.estimator} on {entry.platform}" + spec = SPECS.get(entry.estimator) + assert spec is not None, f"{where}: no estimator registers this id" + assert entry.version == spec.version, f"{where}: version {entry.version}, the estimator is {spec.version}" + assert set(entry.platform) - {"os"} == set(spec.platform_keys), f"{where}: the spec keys {spec.platform_keys}" + platforms = [ + p for p in MEASURED_PLATFORMS if all(getattr(p, key) == value for key, value in entry.platform.items()) + ] + assert platforms, f"{where}: not a measured platform" + assert entry_key(entry) not in seen, f"{where}: declared twice" + seen.add(entry_key(entry)) + for platform in platforms: + shipped_coefficients(spec, platform) @pytest.mark.parametrize("spec", sorted(SPECS.values(), key=lambda spec: spec.id), ids=lambda spec: spec.id) -def test_every_estimator_has_coefficients_on_every_measured_platform_value(spec: EstimatorSpec) -> None: - for platform in _platforms(measured=True): +def test_every_estimator_has_coefficients_on_every_measured_platform(spec: EstimatorSpec) -> None: + for platform in MEASURED_PLATFORMS: assert set(shipped_coefficients(spec, platform).values) == spec.coefficients @pytest.mark.parametrize("spec", sorted(SPECS.values(), key=lambda spec: spec.id), ids=lambda spec: spec.id) -def test_an_unmeasured_platform_value_is_priced_as_the_measured_one_and_says_so(spec: EstimatorSpec) -> None: - for platform in _platforms(measured=False): - priced_as = Platform( - **{key: UNMEASURED_PRICED_AS.get(key, {}).get(value, value) for key, value in asdict(platform).items()} - ) +def test_the_os_only_matters_where_an_entry_names_it(spec: EstimatorSpec) -> None: + """cuDNN and MIOpen with expandable segments are priced the same on every OS.""" + os_specific = { + (entry.platform["conv_backend"], entry.platform["allocator"]) for entry in ENTRIES if "os" in entry.platform + } + assert os_specific == {("miopen", "native"), ("miopen", "expandable")} + for platform in MEASURED_PLATFORMS: + if (platform.conv_backend, platform.allocator) in os_specific: + continue + values = [shipped_coefficients(spec, replace(platform, os=os)).values for os in ("windows", "linux", "other")] + assert all(dict(v) == dict(values[0]) for v in values), platform + + +@pytest.mark.parametrize("spec", sorted(SPECS.values(), key=lambda spec: spec.id), ids=lambda spec: spec.id) +def test_an_unmeasured_platform_is_priced_as_the_measured_one_at_least_and_says_so(spec: EstimatorSpec) -> None: + for platform in UNMEASURED_PLATFORMS: + measured = priced_as(platform) + assert is_measured(measured) and measured.os == platform.os resolved = shipped_coefficients(spec, platform) - assert resolved.values == shipped_coefficients(spec, priced_as).values - assert resolved.source.startswith( - f"shipped:conv_backend={platform.conv_backend} ({priced_as.conv_backend} values" - ) + borrowed = shipped_coefficients(spec, measured) + assert all(resolved.values[name] >= value for name, value in borrowed.values.items()) + where = ",".join(f"{key}={value}" for key, value in _keys(measured, spec).items()) + assert resolved.source.startswith(f"shipped:conv_backend={platform.conv_backend},") + assert f"(priced as {where}" in resolved.source -def test_every_carried_value_is_decided_by_a_measured_point() -> None: +def test_every_fitted_value_is_decided_by_an_in_fit_point() -> None: """A value no point decides is unguarded: any number would pass. An estimator without points of its own takes a - measured one's values with `values_from`, and is then held to that one's points.""" - entries = { - (entry.estimator, entry.version, _platform_key(entry.platform)): entry - for data in shipped_data().values() - for entry in data.coefficients - } + measured one's values with `values_from`, and is then held to that one's points. Only declared stand-ins are + exempt, and they are listed in the entry.""" decided: set[tuple[tuple[str, int, tuple[tuple[str, str], ...]], str]] = set() - for data in shipped_data().values(): - for point in data.points: - key = (point.estimator, point.version, _platform_key(point.platform)) - lender = entries[key].values_from - owner = (lender.estimator, lender.version, key[2]) if lender is not None else key - estimate = _evaluate(point) - decided |= {(owner, term.coefficient) for term in estimate.terms if term.included and term.coefficient} + for point in ALL_POINTS: + if point.in_fit: + owner = entry_key(carrier_of(ENTRIES, point)) + decided |= {(owner, t.coefficient) for t in _evaluate(point).terms if t.included and t.coefficient} undecided = [ f"{entry.estimator} {entry.platform} {name}" - for key, entry in entries.items() + for entry in ENTRIES for name in entry.values or {} - if (key, name) not in decided + if (entry_key(entry), name) not in decided and name not in entry.stand_ins ] assert not undecided, f"no measured point decides these shipped values: {undecided}" +def test_every_fitted_value_is_the_shipped_fit_of_its_points() -> None: + """The values are reproducible from the points rather than hand-tuned. A new point that moves a fit fails here, + naming the values to ship.""" + fitted = fitted_values(shipped_data().values()) + stale = {} + for entry in ENTRIES: + if entry.values is None: + continue + expected = fitted.get(entry_key(entry), {}) + differs = { + name: expected.get(name) + for name, value in entry.values.items() + if name not in entry.stand_ins and expected.get(name) != value + } + if differs: + stale[f"{entry.estimator} {entry.platform}"] = differs + assert not stale, f"ship these fitted values: {stale}" + + +def test_a_stand_in_is_a_whole_line_no_in_fit_point_measures() -> None: + """A stand-in is for what a platform has not measured. Once an in-fit point measures its line, the line is fitted + instead, and this names it.""" + groups = fit_points(shipped_data().values()) + for entry in ENTRIES: + lines = {name[2:] for name in entry.stand_ins} + assert {f"{part}_{line}" for line in lines for part in ("k", "b")} == set(entry.stand_ins), entry.platform + measured = sorted(line for line in lines if (entry_key(entry), line) in groups) + assert not measured, f"{entry.estimator} on {entry.platform}: fit {measured}, they are measured now" + + +def test_the_out_of_fit_points_are_the_listed_ones() -> None: + """Leaving the fit removes a point's upper bound, so it is never a quiet flag flip.""" + out_of_fit = collections.Counter( + (tuple(sorted(point.platform.items())), point.device, point.method) for point in ALL_POINTS if not point.in_fit + ) + assert out_of_fit == OUT_OF_FIT + + +def test_every_listed_upper_bound_exception_is_a_shipped_point_that_needs_it() -> None: + """A stale exception would let a new point at that id over-reserve unseen.""" + ids = {_point_id(point): point for point in ALL_POINTS} + for point_id, ratio in UPPER_BOUND_EXCEPTIONS.items(): + point = ids.get(point_id) + assert point is not None, f"{point_id}: no such point" + assert point.in_fit + reserved = _evaluate(point).reservation_bytes / point.measured_bytes + assert MAX_OVER_RESERVATION < reserved <= ratio, f"{point_id}: {reserved:.3f}x" + + @pytest.mark.parametrize("point", POINTS) def test_every_measured_point_is_bounded_by_its_estimate(point: MeasuredPoint) -> None: estimate = _evaluate(point) @@ -168,8 +252,10 @@ def test_every_measured_point_is_bounded_by_its_estimate(point: MeasuredPoint) - assert reserved >= point.measured_bytes, ( f"reserves {reserved / 2**30:.3f} GiB for a measured {point.measured_bytes / 2**30:.3f} GiB" ) - if point.max_ratio is not None or fitted: - max_ratio = point.max_ratio if point.max_ratio is not None else MAX_OVER_RESERVATION + # An out-of-fit point checks another term: it gets the lower bound only. + if point.max_ratio is not None or (fitted and point.in_fit): + default = UPPER_BOUND_EXCEPTIONS.get(_point_id(point), MAX_OVER_RESERVATION) + max_ratio = point.max_ratio if point.max_ratio is not None else default assert estimate.reservation_bytes <= max_ratio * point.measured_bytes, ( f"reserves {estimate.reservation_bytes / point.measured_bytes:.2f}x the measured peak, over {max_ratio}x" ) @@ -177,10 +263,9 @@ def test_every_measured_point_is_bounded_by_its_estimate(point: MeasuredPoint) - def test_a_point_bound_is_never_looser_than_the_default() -> None: """`max_ratio` tightens the bound for a point the estimate has to stay close to; it never excuses one.""" - for name, data in shipped_data().items(): - for point in data.points: - if point.max_ratio is not None: - assert 1.0 <= point.max_ratio < MAX_OVER_RESERVATION, f"{name}: {_point_id(point)}" + for point in ALL_POINTS: + if point.max_ratio is not None: + assert 1.0 <= point.max_ratio < MAX_OVER_RESERVATION, _point_id(point) @pytest.mark.parametrize( @@ -197,26 +282,95 @@ def test_a_point_bound_is_never_looser_than_the_default() -> None: ) def test_the_platform_is_the_convolution_backend_the_device_really_runs(device, hip, conv_backend, monkeypatch) -> None: monkeypatch.setattr(torch.version, "hip", hip) - assert platform_for(device) == Platform(conv_backend=conv_backend) + assert platform_for(device).conv_backend == conv_backend + + +HOST_OS = platform_module._host_os +"""Bound at import, before the suite's fixture replaces it.""" + +ALLOC_VARS = ("PYTORCH_ALLOC_CONF", "PYTORCH_CUDA_ALLOC_CONF", "PYTORCH_HIP_ALLOC_CONF") + + +@pytest.mark.parametrize("var", ALLOC_VARS) +@pytest.mark.parametrize( + "backend, alloc_conf, allocator", + [ + ("native", None, "native"), + ("native", "expandable_segments: True", "expandable"), + # What Invoke sets for a ROCm build on Windows. + ("native", ROCM_WINDOWS_ALLOC_CONF, "expandable"), + ("native", "expandable_segments:False", "native"), + # The driver's pool ignores expandable segments. + ("cudaMallocAsync", "backend:cudaMallocAsync,expandable_segments:True", "cudaMallocAsync"), + ], +) +def test_the_platform_is_the_allocator_a_gpu_allocates_through( + var, backend, alloc_conf, allocator, monkeypatch +) -> None: + for name in ALLOC_VARS: + monkeypatch.delenv(name, raising=False) + if alloc_conf is not None: + monkeypatch.setenv(var, alloc_conf) + monkeypatch.setattr(torch.cuda, "is_available", lambda: True) + monkeypatch.setattr(torch.cuda, "get_allocator_backend", lambda: backend) + + assert platform_for(torch.device("cuda", 0)).allocator == allocator + # Nothing but a CUDA/HIP GPU allocates through torch's caching allocator. + assert platform_for(torch.device("cpu")).allocator == "native" + assert platform_for(torch.device("mps")).allocator == "native" + + +@pytest.mark.parametrize( + "sys_platform, os", [("win32", "windows"), ("linux", "linux"), ("darwin", "other"), ("freebsd14", "other")] +) +def test_the_platform_is_the_os_the_process_runs_on(sys_platform, os, monkeypatch) -> None: + # The suite pins `_host_os` to Linux (tests/conftest.py); this is the function it replaces. + monkeypatch.setattr(platform_module.sys, "platform", sys_platform) + assert HOST_OS() == os + monkeypatch.setattr(platform_module, "_host_os", HOST_OS) + assert platform_for(torch.device("cpu")).os == os + + +@pytest.mark.parametrize( + "platform, measured", + [ + (("mps", "native", "linux"), ("cudnn", "native", "linux")), + (("xpu", "native", "windows"), ("cudnn", "native", "windows")), + (("cpu", "native", "other"), ("cudnn", "native", "other")), + (("cudnn", "expandable", "windows"), ("cudnn", "native", "windows")), + (("miopen", "cudaMallocAsync", "linux"), ("miopen", "native", "linux")), + # Expandable segments on MIOpen are measured on Windows only, where they are Invoke's default. + (("miopen", "expandable", "windows"), ("miopen", "expandable", "windows")), + (("miopen", "expandable", "linux"), ("miopen", "native", "linux")), + (("miopen", "expandable", "other"), ("miopen", "native", "other")), + ], +) +def test_an_unmeasured_platform_takes_cudnn_or_its_backends_native_allocator(platform, measured) -> None: + assert priced_as(Platform(*platform)) == Platform(*measured) + assert is_measured(Platform(*measured)) + assert {m for m in MEASURED if m[2] is not None} == {("miopen", "expandable", "windows")} def _no_terms(features, values): return () -def _spec(spec_id: str, version: int = 1) -> EstimatorSpec: +def _spec(spec_id: str, version: int = 1, coefficients: frozenset[str] = frozenset({"k"})) -> EstimatorSpec: return EstimatorSpec( - id=spec_id, version=version, coefficients=frozenset({"k"}), platform_keys=("conv_backend",), terms=_no_terms + id=spec_id, version=version, coefficients=coefficients, platform_keys=("conv_backend",), terms=_no_terms ) -def _entry(estimator: str, *, version: int = 1, values=None, values_from: str | None = None) -> CoefficientEntry: +def _entry( + estimator: str, *, version: int = 1, values=None, values_from: str | None = None, floor=None, os=None +) -> CoefficientEntry: return CoefficientEntry( estimator=estimator, version=version, - platform={"conv_backend": "cudnn"}, + platform={"conv_backend": "cudnn"} | ({"os": os} if os else {}), values=values, values_from=ValuesFrom(estimator=values_from, version=1) if values_from else None, + unmeasured_floor=floor or {}, provenance="-", ) @@ -225,38 +379,79 @@ def _data(*entries: CoefficientEntry) -> list[WorkingMemoryData]: return [WorkingMemoryData(schema_version=1, coefficients=list(entries), points=[])] +CUDNN = Platform("cudnn", "native", "linux") + + def test_a_missing_or_stale_entry_names_the_estimator_and_platform() -> None: """No fallback to another platform or version: coefficients fitted for something else are the shortfall the data exists to prevent.""" spec = _spec("test.estimator", version=2) assert resolve_coefficients( - _data(_entry(spec.id, version=2, values={"k": 7.0})), spec, Platform("cudnn"), origin="test" + _data(_entry(spec.id, version=2, values={"k": 7.0})), spec, CUDNN, origin="test" ) == ResolvedCoefficients(values={"k": 7.0}, source="test:conv_backend=cudnn") with pytest.raises(LookupError, match=r"'test\.estimator' version 2 on conv_backend=miopen\."): - resolve_coefficients(_data(_entry(spec.id, version=2, values={"k": 7.0})), spec, Platform("miopen"), "test") + resolve_coefficients( + _data(_entry(spec.id, version=2, values={"k": 7.0})), spec, replace(CUDNN, conv_backend="miopen"), "test" + ) with pytest.raises(LookupError, match=r"'test\.estimator' version 2 on conv_backend=cudnn \(found only version"): - resolve_coefficients(_data(_entry(spec.id, version=1, values={"k": 7.0})), spec, Platform("cudnn"), "test") + resolve_coefficients(_data(_entry(spec.id, version=1, values={"k": 7.0})), spec, CUDNN, "test") with pytest.raises(LookupError, match=r"declared 2 times"): resolve_coefficients( _data(_entry(spec.id, version=2, values={"k": 7.0}), _entry(spec.id, version=2, values={"k": 8.0})), spec, - Platform("cudnn"), + CUDNN, "test", ) +def test_an_entry_that_names_the_os_takes_precedence_there_and_only_there() -> None: + data = _data( + _entry("test.lender", values={"k": 7.0}), + _entry("test.lender", values={"k": 9.0}, os="windows"), + _entry("test.borrower", values_from="test.lender"), + ) + on_windows = resolve_coefficients(data, _spec("test.borrower"), replace(CUDNN, os="windows"), origin="test") + assert on_windows == ResolvedCoefficients( + values={"k": 9.0}, source="test:conv_backend=cudnn,os=windows (values of test.lender)" + ) + on_linux = resolve_coefficients(data, _spec("test.borrower"), CUDNN, origin="test") + assert dict(on_linux.values) == {"k": 7.0} + + def test_borrowed_values_resolve_one_hop_and_the_source_names_both_borrowings() -> None: data = _data(_entry("test.lender", values={"k": 7.0}), _entry("test.borrower", values_from="test.lender")) - on_cudnn = resolve_coefficients(data, _spec("test.borrower"), Platform("cudnn"), origin="test") + on_cudnn = resolve_coefficients(data, _spec("test.borrower"), CUDNN, origin="test") assert on_cudnn == ResolvedCoefficients(values={"k": 7.0}, source="test:conv_backend=cudnn (values of test.lender)") - on_xpu = resolve_coefficients(data, _spec("test.borrower"), Platform("xpu"), origin="test") + on_xpu = resolve_coefficients(data, _spec("test.borrower"), replace(CUDNN, conv_backend="xpu"), origin="test") assert on_xpu == ResolvedCoefficients( - values={"k": 7.0}, source="test:conv_backend=xpu (cudnn values of test.lender)" + values={"k": 7.0}, source="test:conv_backend=xpu (priced as conv_backend=cudnn, values of test.lender)" ) +def test_a_borrower_reads_the_lines_it_runs_of_a_lender_that_carries_more() -> None: + """A family member that runs fewer dtypes borrows from the one that runs them all; it sees only its own.""" + data = _data( + _entry("test.lender", values={"k": 7.0, "k_other": 9.0}), _entry("test.borrower", values_from="test.lender") + ) + resolved = resolve_coefficients(data, _spec("test.borrower"), CUDNN, origin="test") + assert dict(resolved.values) == {"k": 7.0} + # An entry's own values are exactly what its spec reads: an extra one would be a value nothing checks. + with pytest.raises(LookupError, match=r"provide \['k', 'k_other'\]"): + resolve_coefficients(data, _spec("test.lender"), CUDNN, origin="test") + + +def test_an_unmeasured_platform_raises_a_value_to_its_floor_and_a_measured_one_does_not() -> None: + data = _data(_entry("test.lender", values={"k": 7.0, "j": 5.0}, floor={"k": 8.0, "j": 4.0})) + spec = _spec("test.lender", coefficients=frozenset({"k", "j"})) + + assert dict(resolve_coefficients(data, spec, CUDNN, origin="test").values) == {"k": 7.0, "j": 5.0} + on_mps = resolve_coefficients(data, spec, replace(CUDNN, conv_backend="mps"), origin="test") + assert dict(on_mps.values) == {"k": 8.0, "j": 5.0} + assert on_mps.source == "test:conv_backend=mps (priced as conv_backend=cudnn, floored)" + + @pytest.mark.parametrize( "entries, message", [ @@ -276,7 +471,7 @@ def test_borrowed_values_resolve_one_hop_and_the_source_names_both_borrowings() ) def test_a_borrowing_that_is_not_one_hop_to_matching_values_is_refused(entries, message) -> None: with pytest.raises(LookupError, match=message): - resolve_coefficients(_data(*entries), _spec("a"), Platform("cudnn"), origin="test") + resolve_coefficients(_data(*entries), _spec("a"), CUDNN, origin="test") @pytest.mark.parametrize("values, values_from", [({"k": 1.0}, "b"), (None, None)], ids=["both", "neither"]) @@ -285,6 +480,13 @@ def test_an_entry_carries_exactly_one_of_values_and_values_from(values, values_f _entry("a", values=values, values_from=values_from) +def test_a_floor_or_stand_in_names_a_value_the_entry_carries() -> None: + with pytest.raises(ValidationError, match=r"unmeasured floors \['j'\]"): + _entry("a", values={"k": 1.0}, floor={"j": 2.0}) + with pytest.raises(ValidationError, match=r"stand-ins \['j'\]"): + CoefficientEntry(estimator="a", version=1, platform={}, values={"k": 1.0}, stand_ins=["j"], provenance="-") + + def test_a_data_directory_without_data_files_is_named(tmp_path: Path) -> None: with pytest.raises(FileNotFoundError, match=re.escape(str(tmp_path))): load_data(tmp_path) diff --git a/tests/conftest.py b/tests/conftest.py index 28e32876bf9..87a7e610400 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -118,6 +118,18 @@ def _fast_service_polling(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(image_index_default, "_POLL_SECONDS", 0.02) +@pytest.fixture(autouse=True) +def _working_memory_platform_from_the_test_not_the_host(monkeypatch: pytest.MonkeyPatch) -> None: + """A working-memory estimate prices the allocator torch is configured with and the OS it runs on. Read from the + host, a developer's `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` or a Windows runner moves every expected + value. Tests start from no allocator configuration on Linux and set what they are about.""" + from invokeai.backend.working_memory import platform + + for var in ("PYTORCH_ALLOC_CONF", "PYTORCH_CUDA_ALLOC_CONF", "PYTORCH_HIP_ALLOC_CONF"): + monkeypatch.delenv(var, raising=False) + monkeypatch.setattr(platform, "_host_os", lambda: "linux") + + @pytest.fixture def mock_sqlite_database() -> Database: """The in-memory database behind `mock_services`, for tests that need SQL of their own or a service that