diff --git a/docs/src/content/docs/development/Architecture/model-manager.mdx b/docs/src/content/docs/development/Architecture/model-manager.mdx index ea96dcecc10..b72fb5f2524 100644 --- a/docs/src/content/docs/development/Architecture/model-manager.mdx +++ b/docs/src/content/docs/development/Architecture/model-manager.mdx @@ -1053,21 +1053,21 @@ with vae_info.model_on_device(working_memory=estimate) as (_, vae): An estimator that declares an `EstimatorSpec` returns a `WorkingMemoryEstimate` (`invokeai/backend/working_memory/estimate.py`): the reservation, the terms it adds up to, the features it was evaluated from, and the source of its coefficients, which `model_on_device()` logs at debug level. A plain byte count goes in as `working_mem_bytes=` instead; never both. The cache never reserves less than `device_working_mem_gb`. -To write such an estimator (`invokeai/backend/util/vae_working_memory.py` has five for the VAEs, `invokeai/backend/z_image/denoise_working_memory.py` and `invokeai/backend/moss_image/working_memory.py` one each for a transformer forward, the Z-Image and the MOSS-Image denoise's): +To write such an estimator (`invokeai/backend/util/vae_working_memory.py` has five for the VAEs, `invokeai/backend/z_image/denoise_working_memory.py`, `invokeai/backend/flux2/denoise_working_memory.py` and `invokeai/backend/moss_image/working_memory.py` one each for a transformer forward, the Z-Image, the FLUX.2 and the MOSS-Image denoise's): - 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, the dtype the operation runs in, score-matrix bytes, a quantized model's dequantization transient) 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. -- Declare the line the fitted terms price on with `line=` (a function returning a `fit.Line`): the suffix that names the line's coefficients, the operation's size on its x-axis, whether a measurement of these features fits the line, and how point ids label them. Every estimator declares it and nothing defaults it, so a new estimator cannot inherit another's line, and with it which of its measurements fit. The VAE estimators declare the AutoencoderKL convolution line in output pixels (`fit.convolution_line`), the Z-Image denoise one in attended tokens. The fit subtracts nothing from a point's demand, so a line admits only measurements whose demand is the line's alone: an operation with a term of its own on top of the line (a tile, an in-place upcast, a ControlNet's hints, regional prompting's bias, LoRAs, a dequantization or fp8 cast transient, a materialized score matrix the estimate adds rather than takes the larger of) bounds its estimate but fits nothing. +- Declare the line the fitted terms price on with `line=` (a function returning a `fit.Line`): the suffix that names the line's coefficients, the operation's size on its x-axis, whether a measurement of these features fits the line, and how point ids label them. Every estimator declares it and nothing defaults it, so a new estimator cannot inherit another's line, and with it which of its measurements fit. The VAE estimators declare the AutoencoderKL convolution line in output pixels (`fit.convolution_line`), the Z-Image and FLUX.2 denoise ones in attended tokens. The fit subtracts nothing from a point's demand, so a line admits only measurements whose demand is the line's alone: an operation with a term of its own on top of the line (a tile, an in-place upcast, a ControlNet's hints, regional prompting's bias, LoRAs, a dequantization or fp8 cast transient, a materialized score matrix the estimate adds rather than takes the larger of) bounds its estimate but fits nothing. - Opt in with `user_calibrated=True` where a user's calibration may move the estimator: the five VAE estimators do. An estimator that does not is shipped-fit only; a calibration run still records its windows. The calibration summary and plan are built on pixel lines (they key sizes by `width` and `height`), so an estimator on another line, such as the Z-Image denoise's in tokens, must not opt in until they key sizes by `Line.size`. - Bump `version` whenever `terms` changes the formula. - Key the spec by the platform traits its fitted terms depend on (`platform_keys`; the VAE estimators use `conv_backend` and `allocator`), and 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`. `platform_for(device)` reads them from this process; `platform_for_environment(backend, hard)` reads the same facts from a measurement's recorded environment, through the same function. - - `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 for the VAE lines, the legacy constants for the Z-Image denoise's. A fit describes the card it was measured on, not the platforms it stands in for. + - `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 for the VAE lines, the legacy constants for the denoise lines. A fit describes the card it was measured on, not the platforms it stands in for. - 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. - A spec whose fitted terms no platform trait moves declares `platform_keys=()` and ships one entry with an empty `platform`, read alike on every platform, measured or not, never priced as another or floored; its estimate's `source` names it `any platform`, and its points name no platform. MOSS-Image's denoise estimator does this: its fitted term is GEMM and fused-attention activations, not convolution workspaces, and a backend without a fused SDPA kernel is priced by the analytic score-matrix term. - Ship the measured points next to them. A point's `method` says how it was read: one fresh process per point for `reserved_peak` (the allocator's peak reserved bytes), `driver_peak` (the driver-level device usage), and `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); `device_peak`, the drop in the driver's free memory (`torch.cuda.mem_get_info`, polled during the operation) below its value just before it, which also counts what is allocated around the allocator's reserve; or `probe_demand`, the probe's `demand_bytes`, the largest over the valid, fit-eligible windows of calibration runs in a running server. `scripts/working_memory/build_shipped.py` builds `probe_demand` points from calibration store exports, takes fresh-process points from the point files a calibration script writes (`--points`), and refits the values (see the Family E2E guide). A point names the OS only where it measured an OS-specific entry. `working_memory/bounds.py` holds the rule the data test (`tests/backend/working_memory/`) and the builder apply: each estimate is at least its point, and where a fitted term decides an in-fit point, at most 1.3 times the point, its ratio in `bounds.UPPER_BOUND_EXCEPTIONS` (points no line can hold within 1.3x), or the point's tighter `max_ratio`. A `probe_demand` point is a lower bound only (`MeasuredPoint.holds_upper_bound`) and takes no `max_ratio`: a warm window in a running server reads below the cold first run after a server start, the worst case a reservation must cover. It may raise a line; only the fresh-process points cap one. The `device_working_mem_gb` floor counts only where an analytic term decides. -- Fit the values, never hand-tune them. Fitted terms price a line per operation and dtype: `k_` bytes per unit of the line's size plus a base of `b_` bytes. The AutoencoderKL estimators price their convolutions on lines in output pixels (`k__`), the Z-Image denoise its forward on lines in attended tokens (`k_denoise_`). +- Fit the values, never hand-tune them. Fitted terms price a line per operation and dtype: `k_` bytes per unit of the line's size plus a base of `b_` bytes. The AutoencoderKL estimators price their convolutions on lines in output pixels (`k__`), the Z-Image denoise its forward on lines in attended tokens (`k_denoise_`), the FLUX.2 denoise its forward on one such line per dtype and transformer width (`k_denoise__h`). - `working_memory/fit.py` fits each line on the in-fit points its estimators' `line` admits, of every estimator that resolves to the entry: for the convolution lines the untiled single-sample points, leaving out those whose VAE an in-place cast grew (`upcast_weight_bytes`, a term of its own). The fit of the points that hold an upper bound (the fresh-process ones) is the edge of their upper convex hull (with the origin, so the base is never negative) that over-reserves least, rated on the highest reading per size, the steeper on a tie and never a falling one. It needs at least two sizes twice apart. The shipped values add a 5% margin and round up: the slope to a whole byte per unit of size, the base to a whole MiB. A `probe_demand` point is covered by the line and can raise it, but takes no part in that fit. - A point whose demand is not its line's alone sets `in_fit: false`: it fits nothing and gets only the lower bound. That is a point measured in a regime its platform does not run (CUDA with the math attention kernel forced), and a fit-grid forward whose features its estimator's `line` does not admit, such as one whose build materializes the score matrix. The test lists every such point. - A line is fitted only once its in-fit fresh-process points alone span two sizes twice apart (`fit.capping_spans`); `fitted_values` refuses any other line. The shipped pair is the fresh-process fit where it covers every other point of the line by the 5% margin. Where it does not, `fit.raised_pair` raises it by the least the upper bounds allow: of the lines at or above it, per coefficient, that cover every point by 5% and keep the upper bound of every point priced on the line (`bounds.line_bounds`, in the line's units: tiled, batched and upcast VAE operations and denoise forwards with an analytic term included, though their line admits them to no fit, read where the line decides their estimate), the one whose reservation averaged over the fresh-process sizes is smallest, the steeper on a tie. The candidates are the lines that ship, a whole byte of slope per unit of size and a whole MiB of base, checked against the conditions exactly, with no allowance. Each condition is a half-plane in slope and base, so the lines that keep them all form a convex region: the search scans each whole MiB of base across that region, taking the lowest whole slope the conditions leave at it, which finds the lowest rounded line or proves there is none (some milliseconds per line on the shipped data). Where no rounded line satisfies them all, also where only a line between whole bytes or MiB would, it refuses with `fit.LineConflict`, naming the probe and the bounds that conflict (each bound by its point's id, measured bytes and the most it allows, since two points can share an id), or saying that the floor itself already breaks a bound. The data then conflicts with the bounds: `build_shipped.py` prints the changes it would make, writes nothing and exits 1, as for a result the bounds reject; it exits 2 only for input it cannot use. A `probe_demand` point thus lowers neither the slope nor the base, and since probe sizes do not enter the average, a probe the shipped line already covers leaves it where it is. @@ -1079,6 +1079,12 @@ To write such an estimator (`invokeai/backend/util/vae_working_memory.py` has fi - The line admits only the plain forward, measured with a fused attention kernel: every analytic term zero and no attention mask, read off the estimator's terms, so a term added later takes the forwards it prices out of the fit. A point whose score matrix is materialized fits nothing, because the score term is an upper bound, and fitting the demand less the term would leave the line low by the term's slack, short for a build of the same platform keys that runs a fused kernel. A ControlNet's hints (n + 4 hint-sized tensors) and a forced math kernel's score matrix are checked against measurements of their own in `tests/app/invocations/test_z_image_denoise_working_memory.py`. - An fp8-stored transformer reserves the per-forward cast of its weights as a term of its own (`util/fp8.py`, `peak_fp8_cast_transient_bytes`): the largest layer's float8 weights at the compute dtype, twice for a scaled checkpoint weight. The legacy base covers that cast too, so wherever the legacy constants price, such a model reserves it twice. - Denoise lines are shipped-fit only: `denoise.z_image` is not `user_calibrated`. +- The FLUX.2 denoise (`denoise.flux2`, `data/denoise.json`) prices its activations as `k_denoise_bf16_h` bytes per attended token and sample plus a base of `b_denoise_bf16_h`, a line per transformer width (Klein 4B 3072, Klein 9B 4096, [dev] 6144; an unrecognized variant prices as the widest). The attended tokens are the caption's, the image's and every reference image's, and the line's size is those times the batch. The node runs bf16 only. + - The lines are fitted on fresh-process points of `scripts/calibrate_flux2_denoise_working_memory.py` (per width 512 to 2048 px with a 512-token caption, 1024 and 1440 px with a 77-token one, at 1024 px one and three 1024 px references and a batch of two, and long sequences up to 2048 px with one 2024 px reference (33025 tokens), on a random-init transformer of the loader's geometry with 2 blocks per stream): an RTX 4090 with the native allocator and with `cudaMallocAsync`, and an RX 9060 XT under ROCm 10 with AOTriton's fused kernels, with expandable segments and with the native allocator on Windows. On the RX 9060 XT [dev] does not fit 16 GB above 1440 px or with three references, so its [dev] lines there come from 512 to 1440 px, one reference included; every other line is measured up to 33025 tokens where the forward fits the card. The node caps neither the image's side nor the number of references, so a longer sequence prices on its line extrapolated linearly: fused attention keeps the activations linear in the sequence, and from 16896 to 33025 tokens the line reserves a flat 1.05 to 1.13 times what each measured forward demands under the native allocator and expandable segments; under `cudaMallocAsync` the 33025-token forwards decide the line (1.05). Several references are resized to 1024 px each, as the node resizes them. 4 blocks per stream measured the same as 2 on the RTX 4090 and 5 to 7% more on the RX 9060 XT. In a running server the real models demanded less than the 2-block point at the same tokens: an fp8 Klein 4B and 9B 0.96 to 0.99 times it on the RTX 4090 (native allocator), a bf16 Klein 4B 0.81 to 0.95 and an fp8 Klein 9B 0.71 to 0.77 (after its fp8 cast term) on the RX 9060 XT (expandable segments). The Klein 4B line holds its 1440 px forward with a 77-token caption at 1.42x and 1.40x under the native allocator, listed in `bounds.UPPER_BOUND_EXCEPTIONS`. ROCm with the native allocator off Windows has no points and keeps the legacy constants the node reserved by hand, 0.42 MiB per token at width 4096 scaled linearly by the width and truncated per width (330300, 440401 and 660601 bytes) and a 1 GiB base, which `tests/app/invocations/test_flux2_denoise_working_memory.py` holds byte for byte to the formula they replace; they lie above every measured line. The entries unmeasured platforms borrow (cuDNN with the native allocator, MIOpen with the native allocator on Windows) floor every value at those constants, as the Z-Image entries do. A forced math kernel measured 8.74 bytes per score element over the default kernel on the RTX 4090, against the 17 the score term charges. + - The line admits the plain forward, reference images and batches included: a batch adds sequences, which the line prices, and its base is paid once per forward. Every forward with an analytic term (regional prompting's bias, a materialized score matrix, LoRAs, a dequantization or fp8 cast transient) or a mask bounds its estimate but fits nothing. An fp8-stored transformer reserves its cast transient on top, which the legacy base covers too. + - Two analytic terms price what no fit point measures. Every point is a fully resident model, but the cache may load the transformer partially, and then copies each RAM-held layer to the device for its forward, alive beside the activations: the estimate reserves the largest layer as stored, read off the transformer (the largest module's own parameters and buffers, `calc_tensor_size`): in bf16 the single-stream blocks' fused projection of `9 × width²` elements, 679,477,248 bytes for [dev]; for an fp8-stored or GGUF build its float8 or quantized bytes, whose widened copy the fp8 cast and dequantization terms already price. A real [dev] in bf16 with 15.0 of 64.4 GB resident on the RTX 4090 (native allocator) demanded 5509 MB at 8704 tokens (1024 px with one 1024 px reference), 5459 MB at 8612 (1440 px) and 3305 MB at 4608 (1024 px), 535, 518 and 719 MB above the resident 2-block points; the line alone covers 0.96, 0.96 and 0.85 times that, with this term 1.08, 1.08 and 1.05 times. It is charged on every forward, because whether the lock loads the model whole is decided after, and from, the estimate; the calibration script passes `weights_resident=True` for its resident models. With a regional mask, the forward runs on CUDA's memory-efficient kernel, which materializes the bias again in a layout of its own: on top of the `S × S` bias the estimate reserves one copy per sample with its last dimension padded to a multiple of 8. On the RTX 4090 a 2-block Klein 9B-width forward at 1440 px with two 512-token regional captions (S = 9124, a 166.5 MB bias) took 158 MB more of the driver's figure and 168 MB more reserved than the unmasked one, about one bias-sized transient. + - CUDA on Linux prices with the RTX 4090's Windows-measured lines without the unmeasured floor, by judgement rather than measurement: the activations come from the same allocator and kernels on both systems, and the Windows driver model maps more per allocation if anything. + - `denoise.flux2` is not `user_calibrated`. #### Measuring estimates: the probe diff --git a/invokeai/app/invocations/flux2/flux2_denoise.py b/invokeai/app/invocations/flux2/flux2_denoise.py index a8a8fdc420d..f46b0855b6d 100644 --- a/invokeai/app/invocations/flux2/flux2_denoise.py +++ b/invokeai/app/invocations/flux2/flux2_denoise.py @@ -32,6 +32,7 @@ from invokeai.backend.flux.sampling_utils import clip_timestep_schedule_fractional from invokeai.backend.flux.schedulers import FLUX_SCHEDULER_LABELS, FLUX_SCHEDULER_MAP, FLUX_SCHEDULER_NAME_VALUES from invokeai.backend.flux2.denoise import denoise +from invokeai.backend.flux2.denoise_working_memory import estimate_flux2_denoise_working_memory from invokeai.backend.flux2.extensions.regional_prompting_extension import Flux2RegionalPromptingExtension from invokeai.backend.flux2.ref_image_extension import Flux2RefImageExtension from invokeai.backend.flux2.sampling_utils import ( @@ -57,34 +58,17 @@ ) from invokeai.backend.patches.lora_conversions.flux_lora_constants import FLUX_LORA_TRANSFORMER_PREFIX from invokeai.backend.patches.model_patch_raw import ModelPatchRaw -from invokeai.backend.quantization.dequantizing_linear import ( - peak_dequant_transient_bytes, - requires_sidecar_patching, -) +from invokeai.backend.quantization.dequantizing_linear import requires_sidecar_patching from invokeai.backend.rectified_flow.rectified_flow_inpaint_extension import RectifiedFlowInpaintExtension from invokeai.backend.stable_diffusion.diffusers_pipeline import ( PipelineIntermediateState, image_resized_to_grid_as_tensor, ) from invokeai.backend.stable_diffusion.diffusion.conditioning_data import FLUXConditioningInfo -from invokeai.backend.util.attention import sdpa_score_matrix_bytes from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.sage_attention import sage_attention_scope +from invokeai.backend.working_memory import probe -# FLUX.2 attention geometry. The head dim is 128 across every variant and the head count follows -# the hidden size (Klein 4B: 3072/24, Klein 9B: 4096/32, [dev] 6144/48), so the width is the single -# number that describes both. Only the head dim decides which SDPA kernel is eligible; the head -# count scales the `math` fallback's score matrix. -FLUX2_ATTENTION_HEAD_DIM = 128 -# The width the per-token constant below was measured on. Estimates scale off this. -FLUX2_REFERENCE_HIDDEN_SIZE = 4096 -# Peak reserved activation bytes per attended token at the reference width. Measured slope between -# 4608 and 9216 tokens: 0.3859 MB/tok on CUDA (RTX 4090, torch 2.7.1) and 0.4067 on ROCm (RX 9070 -# XT, gfx1201, torch 2.10). 0.42 is an upper bound on both -- it was 0.4, which the ROCm point -# exceeds. The margin is deliberately small because this is the dominant term at every resolution. -FLUX2_BYTES_PER_TOKEN_AT_REFERENCE_WIDTH = int(0.42 * 1024**2) -# The widest variant, used when the config does not tell us which one this is -- over-reserving on -# an unknown model beats under-reserving on the largest one. FLUX2_MAX_HIDDEN_SIZE = 6144 @@ -480,42 +464,7 @@ def _run_diffusion(self, context: InvocationContext) -> torch.Tensor: bn_std=bn_std, ) - # Estimate the peak activation memory the transformer forward will need and ask the model cache - # to keep that much VRAM free. Without this hint the cache reserves only the small default - # working memory and fills the rest of the card with the model, so anything beyond a plain - # low-resolution generation OOMs. Reference images are the dominant term: their latents are - # concatenated onto the image stream, so three 1024x1024 references quadruple the sequence - # (and with it the activation footprint) of a 1024x1024 generation. ref_image_seq_len = ref_image_extension.ref_image_latents.shape[1] if ref_image_extension is not None else 0 - # The additive bias is skipped entirely when reference images are present (see below), so the - # mask only costs anything -- storage, and possibly a materialized score matrix -- without them. - regional_attn_mask = regional_extension.restricted_attn_mask if ref_image_seq_len == 0 else None - num_control_blocks = len(control_branch_info.model.control_blocks) if control_branch_info is not None else 0 - estimated_working_memory = self._estimate_working_memory( - image_seq_len=packed_h * packed_w, - ref_image_seq_len=ref_image_seq_len, - text_seq_len=max(txt.shape[1], neg_txt.shape[1] if neg_txt is not None else 0), - num_loras=len(self.transformer.loras), - # Taken from `x`, not from `b`. `b` is the *noise* tensor's batch, which this node builds - # at 1 from width/height/seed even when the init latents carry more; the img2img preblend - # above then broadcasts the two, so `x` is the only thing that knows how many samples - # actually go through the transformer. Reference latents are repeated to match it - # (`ensure_batch_size` below), so they scale with it too. - batch_size=x.shape[0], - # Activation cost per token scales with the transformer's width, and [dev] is 1.5x - # Klein 9B. Fall back to the widest variant when the config cannot tell us. - hidden_size=flux2_hidden_size(getattr(transformer_config, "variant", None)) or FLUX2_MAX_HIDDEN_SIZE, - # The mask itself is already allocated; only the additive bias built per forward is new. - regional_attention_bias_bytes=( - regional_attn_mask.numel() * torch.empty((), dtype=inference_dtype).element_size() - if regional_attn_mask is not None - else 0 - ), - has_regional_attention_mask=regional_attn_mask is not None, - device=device, - dtype=inference_dtype, - num_control_blocks=num_control_blocks, - ) # Encoded ahead of the transformer's lock, so the VAE does not have to fit beside it. control_context: torch.Tensor | None = None @@ -528,28 +477,58 @@ def _run_diffusion(self, context: InvocationContext) -> torch.Tensor: with ExitStack() as exit_stack: transformer_info = context.models.load(self.transformer.transformer) - # An `int8_tensorwise` or GGUF build materializes each linear's dequantized weight per forward. - # That transient is not part of the model's resident size, so it is added to the - # activation estimate -- the two are alive at the same time -- rather than taking its - # chances against whatever slack the estimate happens to have. Read from the unlocked - # model, before the VRAM lock the reservation applies to; zero for every other build. - dequant_bytes = peak_dequant_transient_bytes(transformer_info.model, inference_dtype) + # Ask the model cache to keep free what the transformer forward needs. Without it the cache keeps only + # the default working memory and fills the rest of the card with the model, so anything beyond a plain + # low-resolution generation OOMs. Reference images are the dominant term: their latents are concatenated + # onto the image stream, so three 1024x1024 references quadruple the attended sequence of a 1024x1024 + # generation. Priced on the unlocked model, whose quantized or fp8-stored layers add a transient per + # forward, before the VRAM lock the reservation applies to. + working_memory = estimate_flux2_denoise_working_memory( + transformer_info.model, + # Activation cost per token follows the transformer's width, and [dev] is 1.5x Klein 9B. Priced as + # the widest variant when the config cannot tell: over-reserving on an unknown model beats + # under-reserving on the largest one. + hidden_size=flux2_hidden_size(getattr(transformer_config, "variant", None)) or FLUX2_MAX_HIDDEN_SIZE, + image_seq_len=packed_h * packed_w, + ref_image_seq_len=ref_image_seq_len, + text_seq_len=max(txt.shape[1], neg_txt.shape[1] if neg_txt is not None else 0), + # Taken from `x`, not from `b`. `b` is the *noise* tensor's batch, which this node builds at 1 from + # width/height/seed even when the init latents carry more; the img2img preblend above then + # broadcasts the two, so `x` is the only thing that knows how many samples actually go through the + # transformer. Reference latents are repeated to match it (`ensure_batch_size` below). + batch_size=x.shape[0], + lora_count=len(self.transformer.loras), + control_blocks=len(control_branch_info.model.control_blocks) if control_branch_info is not None else 0, + # The additive bias is skipped entirely when reference images are present (see below), so the mask + # only costs anything without them. + regional_attn_mask=regional_extension.restricted_attn_mask if ref_image_seq_len == 0 else None, + device=device, + dtype=inference_dtype, + ) # Load the transformer model (cached_weights, transformer) = exit_stack.enter_context( - transformer_info.model_on_device(working_mem_bytes=estimated_working_memory + dequant_bytes) + transformer_info.model_on_device(working_memory=working_memory) ) config = transformer_config control_gate: Callable[[int], None] | None = None if control_branch_info is not None: assert self.control is not None and control_context is not None - # Locked with the same working memory: the transformer can no longer be offloaded, so a branch placed - # with only the default reservation would fill the room kept for the forward. The loader built the - # branch in bfloat16, this node's inference dtype; the branch casts what crosses into its chain and - # the hints back (`Flux2FunControlBranch.block_runner` / `add_hint`), so nothing cache-managed is cast. + # Locked keeping free what the transformer's lock keeps free: the transformer can no longer be + # offloaded, so a branch placed with only the default reservation would fill the room kept for the + # forward. Inside a calibration scope that is the calibration reservation, as `model_on_device` applies + # it to the transformer, so the branch cannot use up the window's headroom. A plain reservation, not + # the estimate: inside the transformer's calibration window this lock is a co-lock, whose weights go + # into the window's offset rather than its demand, where a second estimate would open a nested window + # that freezes the transformer's for the whole loop. The loader built the branch in bfloat16, this + # node's inference dtype; the branch casts what crosses into its chain and the hints back + # (`Flux2FunControlBranch.block_runner` / `add_hint`), so nothing cache-managed is cast. + _, branch_reservation = probe.lock_reservation( + working_memory.reservation_bytes, control_branch_info.compute_device + ) (_, control_branch) = exit_stack.enter_context( - control_branch_info.model_on_device(working_mem_bytes=estimated_working_memory + dequant_bytes) + control_branch_info.model_on_device(working_mem_bytes=branch_reservation) ) assert isinstance(control_branch, Flux2FunControlBranch) control_hooks = exit_stack.enter_context( @@ -670,109 +649,6 @@ def _prep_inpaint_mask(self, context: InvocationContext, latents: torch.Tensor) mask = mask.to(device=latents.device, dtype=latents.dtype) return mask.expand_as(latents) - def _estimate_working_memory( - self, - image_seq_len: int, - ref_image_seq_len: int, - text_seq_len: int, - num_loras: int, - batch_size: int = 1, - hidden_size: int = FLUX2_REFERENCE_HIDDEN_SIZE, - regional_attention_bias_bytes: int = 0, - has_regional_attention_mask: bool = False, - device: torch.device | None = None, - dtype: torch.dtype = torch.bfloat16, - num_control_blocks: int = 0, - ) -> int: - """Estimate peak transformer activation memory (bytes) so the model cache reserves enough headroom. - - FLUX.2 attention runs through SDPA without materializing the O(seq^2) score matrix, so the - activation footprint scales *linearly* with the total attended sequence -- text tokens, image - tokens, and reference-image tokens alike. Measured on the Klein 9B geometry in bf16 as peak - reserved memory, that slope holds from 1.5k to 28k tokens and is independent of the block - count (a no-grad forward frees each block's intermediates). It is *not* independent of the - build: 0.3859 MB/token on CUDA, 0.4067 on ROCm/gfx1201, so the constant is 0.42. - - It is *not* independent of the transformer's width, which is why ``hidden_size`` is a - parameter rather than a constant. Measured slope between 4608 and 9216 tokens, block count - and everything else held fixed: - - CUDA 3072 (Klein 4B) 0.291 MB/tok 4096 (Klein 9B) 0.386 6144 ([dev]) 0.555 - ROCm/gfx1201 3072 0.315 4096 0.407 6144 0.589 - - which is 0.755 / 1.000 / 1.438 on CUDA and 0.774 / 1.000 / 1.448 on ROCm, against width - ratios of 0.75 / 1.00 / 1.50 -- linear in width on both, - and slightly sub-linear at the top so scaling by width stays an upper bound. Calibrating on - Klein 9B alone would have under-reserved [dev] by a third: 1024x1024 with three 1024x1024 - references is 16896 tokens, ~7.6GB reserved against ~10GB needed on both platforms. The head - count follows the same width, so the score-matrix term gets the real one instead of the - widest. - - The reference-image term is what makes this estimate necessary rather than merely nice to - have: a 1024x1024 generation is 4096 image tokens (~1.7GB), but attaching three 1024x1024 - references adds 12288 more for ~6.5GB, and a 1328px tile with three 1328px references reaches - ~10.9GB -- against a default ``device_working_mem_gb`` of 3. - - A fixed base covers resolution-independent overhead (transient fp8 -> bf16 weight casts during - the forward, and allocator slack across many steps); the dequantization copy of an int8 or GGUF - build is added by the caller. LoRA sidecar patches add an extra - activation branch per patched layer, so we add a per-LoRA margin. - - Batch multiplies the token count and nothing else. A batch of B is B independent sequences, - so it enters the linear term exactly as extra sequence does -- measured on the Klein geometry - with a reduced block count: 4608 tokens at B=1 peaks at 2570MB, the same 4608 at B=2 (9216 - tokens) at 5126MB, and 9728 tokens at B=1 at 5584MB. Batch and sequence are interchangeable - to within the noise. Reference latents are repeated per sample (`ensure_batch_size`), so they - scale with it too, and the score matrix is shaped (batch, heads, S, S). The fixed base does - not scale -- it is about weights, not activations -- and neither does the regional bias, which - is built as (1, 1, S, S) and broadcast across the batch. - - A ControlNet branch runs its blocks over the whole image stream, reference tokens included, and keeps - one hint per block alive until the last listed base block has consumed it. The term reserves - ``num_control_blocks + 4`` hint-sized tensors (batch x stream x width), the coefficient Z-Image measured - for its adapter over the plain forward (``z_image_denoise.py``). It is not measured for FLUX.2. - - The linear model holds only while attention runs on a fused kernel. Regional prompting is - where that stops being a given: it hands the transformer a dense additive ``S x S`` bias, - which flash attention never accepts, leaving whatever else the build has -- and if that is - the ``math`` fallback, a materialized ``heads x S x S`` score matrix. Whether a mask forces - that is build-specific and not worth predicting: CUDA's memory-efficient kernel takes it, and - so does ROCm's on gfx1100. The device decides too (MPS has no fused SDPA kernel at all), and - so does the diffusers attention backend this build dispatches through. - ``sdpa_score_matrix_bytes`` asks all three and adds the score matrix only where it is really - built: on CUDA with the stock backend the term is zero (verified: peak stays linear with the - bias attached). - """ - GB = 1024**3 - per_token_bytes = int(FLUX2_BYTES_PER_TOKEN_AT_REFERENCE_WIDTH * hidden_size / FLUX2_REFERENCE_HIDDEN_SIZE) - total_seq_len = image_seq_len + ref_image_seq_len + text_seq_len - estimated = total_seq_len * batch_size * per_token_bytes - estimated += int(1.0 * GB) - estimated += regional_attention_bias_bytes - estimated += sdpa_score_matrix_bytes( - device=device if device is not None else TorchDevice.choose_torch_device(), - dtype=dtype, - num_heads=(hidden_size // FLUX2_ATTENTION_HEAD_DIM) * batch_size, - head_dim=FLUX2_ATTENTION_HEAD_DIM, - seq_len=total_seq_len, - has_attn_mask=has_regional_attention_mask, - # The FLUX.2 transformer's attention goes through diffusers' `dispatch_attention_fn`, - # which can route around torch's SDPA entirely -- including to a forced `math` backend. - via_diffusers_dispatch=True, - ) - if num_loras > 0: - # A sidecar branch is an activation, so it scales with the batch like the rest of them. - estimated += int(0.5 * num_loras * batch_size * GB) - if num_control_blocks > 0: - estimated += ( - (num_control_blocks + 4) - * (image_seq_len + ref_image_seq_len) - * batch_size - * hidden_size - * dtype.itemsize - ) - return estimated - def _load_control_branch( self, context: InvocationContext, diff --git a/invokeai/backend/flux2/denoise_working_memory.py b/invokeai/backend/flux2/denoise_working_memory.py new file mode 100644 index 00000000000..76cdcb9d6ef --- /dev/null +++ b/invokeai/backend/flux2/denoise_working_memory.py @@ -0,0 +1,286 @@ +"""The working memory of one FLUX.2 transformer forward, as the denoise node reserves it. + +The transformer attends over one joint sequence per sample: the caption's tokens, the image's and those of every +reference image, which `Flux2RefImageExtension` concatenates onto the image stream and repeats per sample. On a fused +SDPA kernel its activations scale linearly with that sequence and with the batch, which runs as that many sequences: +`k_denoise_bf16_h` bytes per attended token per sample plus a base of `b_denoise_bf16_h` bytes, shipped +per platform in `backend/working_memory/data/denoise.json`. The cost per token follows the transformer's width (Klein +4B 3072, Klein 9B 4096, [dev] 6144), so each width has a line of its own. The node runs the transformer in bf16 only. +Conditional and unconditional passes run one after the other, so only the longer caption counts. The platform keys +are those of the VAE estimators; for a transformer, `conv_backend` just names the CUDA (`cudnn`) or ROCm (`miopen`) +stack it runs on. + +That line is `denoise_bf16_h` over the attended tokens times the batch (`_line`), and only the plain forward +fits it: every analytic term below comes to zero, and no attention mask is passed. A batch fits too, because the line +prices exactly what a batch adds, its sequences, and a forward's base is paid once whatever the batch. `_line` reads +the fit off the terms themselves, so a term added here takes the forwards it prices out of the fit. The fit takes a +point's demand as measured, so a forward with an analytic term bounds its estimate but fits nothing: a materialized +score matrix's term is an upper bound, and a line fitted on the demand less that term would come out low by its slack. +The spec does not opt in to user calibration (`EstimatorSpec.user_calibrated`): the lines are the shipped fit's alone. + +The lines are fitted per platform and width on fresh-process points of +`scripts/calibrate_flux2_denoise_working_memory.py` (their provenance is in the data): an RTX 4090 with the native +allocator and with `cudaMallocAsync`, and an RX 9060 XT under ROCm 10 with AOTriton's fused kernels, with expandable +segments and with the native allocator on Windows. Per width they run 512 to 2048px with a 512-token caption, 1024 and +1440px with a 77-token one, at 1024px one and three 1024px references and a batch of two, and long sequences: 2048px +with one 2024px reference (33025 tokens), 1536px with one (25857) and 1328px with three 1328px references, resized to +1024px each (19689). They run on a random-init transformer of the loader's geometry with 2 double-stream and 2 +single-stream blocks, so the lines are measured up to 33025 tokens, except where a forward does not fit the card: [dev] +at 33025 tokens on the RTX 4090 under the native allocator, Klein 9B at 33025 on the RX 9060 XT under the native +allocator, and on the RX 9060 XT [dev] above 1440px and with three references, so its [dev] lines there come from 512 to +1440px, one reference included. Reference images and the batch lie on the line of the plain forwards. 4 blocks per +stream measured the same as 2 on the RTX 4090 and 5 to 7% more on the RX 9060 XT at 1024px (Klein 4B and 9B widths). The +real models in a running server demanded less than the 2-block point at the same tokens: 0.96 to 0.99 times it for an +fp8 Klein 4B and 0.96 to 0.97 for an fp8 Klein 9B on the RTX 4090 under the native allocator, 0.81 to 0.95 for a bf16 +Klein 4B and 0.71 to 0.77 for an fp8 Klein 9B (after its 576 MiB fp8 cast term) on the RX 9060 XT with expandable +segments. On ROCm a process's first forward maps ~245 MiB more than its second, which the base carries. + +The node caps neither the image's side nor the number of references (each of several is resized to 1024px, 4096 tokens), +so a longer sequence prices on its line extrapolated linearly. That is expected to hold: on fused attention kernels the +activations grow linearly with the sequence, and from 16896 to 33025 tokens the line reserves a flat 1.05 to 1.13 times +what each measured forward demands under the native allocator and expandable segments. Under `cudaMallocAsync` the +33025-token forwards decide the line (1.05) and the shorter ones sit up to 1.22 below it. + +ROCm with the native allocator on every OS but Windows is not measured, and keeps the `legacy` constants the node +reserved by hand: 0.42 MiB per attended token at the Klein 9B width, scaled linearly by the width and truncated to +whole bytes per width (330300, 440401 and 660601), and a 1 GiB base. They lie above every measured line. Every other +platform nothing measures is priced as a measured one (`priced_as`), and the two fitted entries those borrow, the RTX +4090's and the RX 9060 XT's on Windows with the native allocator, floor every value at the legacy constants there +(`unmeasured_floor`): no unmeasured platform reserves less than the node did by hand. The legacy base also covers the +fp8 weights cast per forward, which an fp8-stored model reserves as a term of its own as well, so it pays that cast +twice wherever they price. + +CUDA on Linux reuses the RTX 4090's Windows-measured lines as they are, without the unmeasured floor: its entries are +keyed for every OS (`platform.MEASURED`), as the Z-Image and VAE estimators' cuDNN entries are. That is a judgement, +not a measurement. A forward's activations are torch tensors from the same caching allocator over the same attention +and GEMM kernels on both systems; what made MIOpen's Windows lines differ, HIP's runtime and allocator under Windows, has +no counterpart on CUDA, where the Windows driver model, if anything, maps more per allocation than Linux does. A floor +there would charge the legacy 1 GiB base to the platform most FLUX.2 users run on, for a gap nothing has shown. + +The analytic terms: + +- The largest layer's stored weights, for a transformer the model cache may load partially: the weights it leaves in + RAM are copied to the device per forward, one layer at a time, and the copy is alive during its layer's matmul + alongside the activations. No point prices it: every point is a fully resident model. The size is read off the + transformer, the largest module's own parameters and buffers as stored (`calc_tensor_size`, as the cache counts + them): in bf16 the single-stream blocks' fused projection (`to_qkv_mlp_proj`, ``width x 9 width``), 679,477,248 + bytes for [dev]; for an fp8-stored or GGUF build its float8 or quantized bytes, whose widened copy is the fp8 cast or + dequant transient's term. A real [dev] in bf16 with 15.0 of its 64.4 GB resident on the RTX 4090 (native + allocator) demanded 5509 MB at 8704 tokens (1024px with one 1024px reference), 5459 MB at 8612 (1440px) and 3305 MB + at 4608 (1024px), 535, 518 and 719 MB above the resident 2-block points; the line alone reserved 0.96, 0.96 and 0.85 + times that, with this term 1.08, 1.08 and 1.05 times. The node charges it on every forward: whether the cache loads + the transformer whole is decided by the lock this estimate sizes, after it, so residency is unknown when the + estimate is taken. A fully resident model over-reserves one layer. The calibration script, whose models are always + resident, declares them so (`weights_resident`), and the term is zero. +- Regional prompting's additive attention bias, one ``S x S`` tensor in the compute dtype broadcast over the batch and + the heads (`Flux2RegionalPromptingExtension.get_joint_attention_kwargs`). The node passes no mask with reference + images, which the mask does not cover. +- With that bias, a second copy of it per sample, its last dimension padded to a multiple of 8: on CUDA a masked + forward goes to the memory-efficient kernel, which materializes the bias in a layout of its own. On the RTX 4090 + (native allocator, 2-block Klein 9B width, 1440px with two 512-token regional captions, S = 9124, a 166.5 MB bias) + the masked forward took 158 MB more of the driver's figure and 168 MB more reserved than the unmasked one, about one + bias-sized transient, with allocated unchanged; its demand, 3761 MB with the bias, is 1.03 times below what the + estimate reserved without this term. No fit point is masked, so the copy is priced on every platform, aligned or not. +- Where SDPA has no fused kernel for these shapes it materializes the score matrices, shaped (batch, heads, S, S): + `sdpa_score_matrix_bytes` prices them only where they are really built. A mask narrows which fused kernels a build + can use, so the call is told whether one is passed. +- The FLUX.2 [dev] Fun-ControlNet's hints. The branch keeps one hint per block alive until the last base block it feeds + has consumed it, and a hint covers the image stream only, reference tokens included: its double-stream blocks carry a + text stream of their own, but `add_hint` touches only `hidden_states`. `n + 4` hint-sized tensors (batch x image + stream x width x element size) are charged for `n` blocks, the coefficient the Z-Image estimator measured for its + adapter over the plain forward. It is not measured for FLUX.2. +- Half a GiB per LoRA and sample: a sidecar patch adds an activation branch per patched layer. +- The dequantization copy an int8_tensorwise, nvfp4 or GGUF build materializes per quantized linear and forward, + alive alongside the activations and invisible to them (`peak_dequant_transient_bytes`); zero for every other build. +- The compute-dtype copies of a layer's float8-stored weights that FP8 Storage, or a kept fp8 checkpoint, widens per + forward (`peak_fp8_cast_transient_bytes`); zero for every other build. +""" + +from collections.abc import Mapping +from typing import Final, Optional, cast + +import torch + +from invokeai.backend.quantization.dequantizing_linear import peak_dequant_transient_bytes +from invokeai.backend.util.attention import sdpa_score_matrix_bytes +from invokeai.backend.util.calc_tensor_size import calc_tensor_size +from invokeai.backend.util.fp8 import peak_fp8_cast_transient_bytes +from invokeai.backend.working_memory.estimate import FeatureValue, WorkingMemoryEstimate, WorkingMemoryTerm +from invokeai.backend.working_memory.fit import Line, off_line_reasons +from invokeai.backend.working_memory.platform import platform_for +from invokeai.backend.working_memory.spec import EstimatorSpec, register + +FLUX2_ATTENTION_HEAD_DIM: Final = 128 +"""Every variant's head width; the head count follows the hidden size (Klein 4B 24, Klein 9B 32, [dev] 48).""" +FLUX2_HIDDEN_SIZES: Final = (3072, 4096, 6144) +"""The transformer widths with a line: Klein 4B, Klein 9B and [dev].""" + +_BIAS_ALIGNMENT: Final = 8 +"""The last-dimension alignment the memory-efficient attention kernel pads an attention bias to.""" +_CONTROL_HINTS_BEYOND_BLOCKS: Final = 4 +_LORA_PATCH_BYTES: Final = 2**29 +_DTYPE_NAMES: Final[Mapping[torch.dtype, str]] = {torch.bfloat16: "bf16"} +"""The dtypes the node runs the transformer in.""" +_ELEMENT_SIZES: Final[Mapping[str, int]] = {"bf16": 2} +_LINES: Final = tuple(f"denoise_{dtype}_h{width}" for dtype in _DTYPE_NAMES.values() for width in FLUX2_HIDDEN_SIZES) +_COEFFICIENTS: Final = frozenset(f"{part}_{line}" for part in ("k", "b") for line in _LINES) + + +def _suffix(features: Mapping[str, FeatureValue]) -> Optional[str]: + """The line a forward's dtype and width price on, None for a pair that has none.""" + dtype, width = features.get("dtype"), features.get("hidden_size") + suffix = f"denoise_{dtype}_h{width}" + return suffix if suffix in _LINES else None + + +def _line(features: Mapping[str, FeatureValue]) -> Optional[Line]: + """The forward's line, `denoise__h` over the attended tokens of every sample. Only the plain forward + fits it: no attention mask, and every analytic term of `_terms` zero (see the module docstring). The label names + what puts a forward off the line: the mask, and each analytic term that is not zero, in bytes. A dtype or width + without a line names none.""" + suffix = _suffix(features) + tokens, batch = features.get("tokens"), features.get("batch_size") + if suffix is None or not isinstance(tokens, int) or not isinstance(batch, int): + return None + off_line = off_line_reasons(_terms, features, _COEFFICIENTS) + references = features.get("reference_tokens") + shape = "+".join( + ( + str(features.get("image_tokens")), + *([f"{references}ref"] if references else []), + f"{features.get('text_tokens')}tok", + ) + ) + batched = [f"b{batch}"] if batch != 1 else [] + label = "-".join(("denoise", str(features["dtype"]), f"h{features['hidden_size']}", shape, *batched, *off_line)) + return Line(suffix, tokens * batch, not off_line, label) + + +def _terms(features: Mapping[str, FeatureValue], values: Mapping[str, float]) -> tuple[WorkingMemoryTerm, ...]: + suffix = _suffix(features) + if suffix is None: + raise ValueError(f"no denoise line for {features.get('dtype')} at width {features.get('hidden_size')}") + batch = cast(int, features["batch_size"]) + # Absent from the points measured before the control branch existed, all of them forwards without it. + control_blocks = cast(int, features.get("control_blocks", 0)) + hint_bytes = 0 + if control_blocks: + stream = cast(int, features["image_tokens"]) + cast(int, features["reference_tokens"]) + hint_bytes = ( + (control_blocks + _CONTROL_HINTS_BEYOND_BLOCKS) + * stream + * batch + * cast(int, features["hidden_size"]) + * _ELEMENT_SIZES[cast(str, features["dtype"])] + ) + # Both absent from the points measured before these terms existed, which are fully resident forwards without a + # mask: zero is what such a point streams and pads, though a forward the node prices now always carries them. + streamed_layer_bytes = cast(int, features.get("streamed_layer_bytes", 0)) + padded_bias_bytes = cast(int, features.get("padded_bias_bytes", 0)) + return ( + WorkingMemoryTerm.fitted("activations", f"k_{suffix}", cast(int, features["tokens"]) * batch, values), + WorkingMemoryTerm.fitted("base", f"b_{suffix}", 1, values), + WorkingMemoryTerm("regional_attention_bias", cast(int, features["regional_bias_bytes"]), True), + WorkingMemoryTerm("padded_attention_bias", padded_bias_bytes, True), + WorkingMemoryTerm("streamed_layer", streamed_layer_bytes, True), + WorkingMemoryTerm("attention_scores", cast(int, features["score_matrix_bytes"]), True), + WorkingMemoryTerm("control_hints", hint_bytes, True), + WorkingMemoryTerm("lora_patches", cast(int, features["lora_count"]) * batch * _LORA_PATCH_BYTES, True), + WorkingMemoryTerm("dequant_transient", cast(int, features["dequant_transient_bytes"]), True), + WorkingMemoryTerm("fp8_cast_transient", cast(int, features["fp8_cast_transient_bytes"]), True), + ) + + +def _largest_layer_bytes(transformer: torch.nn.Module) -> int: + """The largest module's own parameters and buffers as stored: what partial loading copies for one layer.""" + return max( + ( + sum(calc_tensor_size(t) for t in (*module.parameters(recurse=False), *module.buffers(recurse=False))) + for module in transformer.modules() + ), + default=0, + ) + + +def _padded_bias_bytes(mask: Optional[torch.Tensor], dtype: torch.dtype) -> int: + """One sample's copy of the additive bias in `dtype`, its last dimension padded to `_BIAS_ALIGNMENT`; 0 without one.""" + if mask is None: + return 0 + columns = mask.shape[-1] + padded = -(-columns // _BIAS_ALIGNMENT) * _BIAS_ALIGNMENT + return mask.numel() // columns * padded * dtype.itemsize + + +FLUX2_DENOISE_SPEC = register( + EstimatorSpec( + id="denoise.flux2", + version=2, + coefficients=_COEFFICIENTS, + platform_keys=("conv_backend", "allocator"), + terms=_terms, + line=_line, + ) +) + + +def estimate_flux2_denoise_working_memory( + transformer: torch.nn.Module, + *, + hidden_size: int, + image_seq_len: int, + ref_image_seq_len: int, + text_seq_len: int, + batch_size: int, + lora_count: int, + control_blocks: int = 0, + regional_attn_mask: Optional[torch.Tensor] = None, + weights_resident: bool = False, + device: torch.device, + dtype: torch.dtype, +) -> WorkingMemoryEstimate: + """The working memory one forward of `transformer` needs on `device` in `dtype`. + + `transformer` is the model about to be locked, read unlocked for its dequantization and fp8 cast transients, and + `hidden_size` its width. `image_seq_len`, `ref_image_seq_len` and `text_seq_len` are one sample's token counts, the + longer caption of a CFG pair, and `batch_size` the samples the forward runs. `control_blocks` is the + Fun-ControlNet branch's block count, 0 without one. `regional_attn_mask` is regional prompting's mask, which the + forward passes as an additive bias. `weights_resident` is True only where the caller knows every weight stays on + the device for the forward; otherwise the largest layer, which partial loading would stream, is reserved too. + """ + dtype_name = _DTYPE_NAMES.get(dtype) + if dtype_name is None or hidden_size not in FLUX2_HIDDEN_SIZES: + raise ValueError( + f"Working-memory estimator '{FLUX2_DENOISE_SPEC.id}' has no {dtype} line at width {hidden_size} on " + f"{platform_for(device)}: the transformer runs in a dtype or at a width it is not measured for." + ) + tokens = image_seq_len + ref_image_seq_len + text_seq_len + has_attention_mask = regional_attn_mask is not None + features: dict[str, FeatureValue] = { + "hidden_size": hidden_size, + "image_tokens": image_seq_len, + "reference_tokens": ref_image_seq_len, + "text_tokens": text_seq_len, + "tokens": tokens, + "batch_size": batch_size, + "dtype": dtype_name, + "has_attention_mask": has_attention_mask, + "regional_bias_bytes": regional_attn_mask.numel() * dtype.itemsize if regional_attn_mask is not None else 0, + "padded_bias_bytes": _padded_bias_bytes(regional_attn_mask, dtype) * batch_size, + "streamed_layer_bytes": 0 if weights_resident else _largest_layer_bytes(transformer), + "score_matrix_bytes": sdpa_score_matrix_bytes( + device=device, + dtype=dtype, + num_heads=(hidden_size // FLUX2_ATTENTION_HEAD_DIM) * batch_size, + head_dim=FLUX2_ATTENTION_HEAD_DIM, + seq_len=tokens, + has_attn_mask=has_attention_mask, + # The attention goes through diffusers' `dispatch_attention_fn`, which can route around torch's SDPA, + # including to a forced `math` backend. + via_diffusers_dispatch=True, + ), + "lora_count": lora_count, + "control_blocks": control_blocks, + "dequant_transient_bytes": peak_dequant_transient_bytes(transformer, dtype), + "fp8_cast_transient_bytes": peak_fp8_cast_transient_bytes(transformer, dtype), + } + return FLUX2_DENOISE_SPEC.estimate(features, device) diff --git a/invokeai/backend/model_manager/load/load_base.py b/invokeai/backend/model_manager/load/load_base.py index cdddcbb3ce6..c0a4de5828e 100644 --- a/invokeai/backend/model_manager/load/load_base.py +++ b/invokeai/backend/model_manager/load/load_base.py @@ -261,10 +261,8 @@ def model_on_device( f"Working memory for {self._cache_record.key}: {working_memory.estimator} reserves " f"{working_mem_bytes / 2**20:.0f} MB ({working_memory.source})" ) - scope = probe.current_scope() - if scope is not None and self.compute_device.type != "cpu": - calibration = scope - working_mem_bytes = probe.calibration_reservation(working_mem_bytes) + calibration, working_mem_bytes = probe.lock_reservation(working_mem_bytes, self.compute_device) + if calibration is not None: message += f"; the calibration lock reserves {working_mem_bytes / 2**20:.0f} MB" InvokeAILogger.get_logger(__name__).debug(message) self._lock_on_device(working_mem_bytes, offload_others=calibration is not None) diff --git a/invokeai/backend/util/vae_working_memory.py b/invokeai/backend/util/vae_working_memory.py index 3a71d2974b0..04873f384c9 100644 --- a/invokeai/backend/util/vae_working_memory.py +++ b/invokeai/backend/util/vae_working_memory.py @@ -120,9 +120,10 @@ def _vae_mid_block_score_matrix_bytes( # 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. Every spec here declares that line (`fit.convolution_line`) and opts in to user -# calibration (`user_calibrated`), so a calibration run's decode windows refit these lines per device. +# they are fitted on (`backend/working_memory/fit.py`): calibration runs in a running server measure them on a card, +# and `scripts/working_memory/build_shipped.py` turns their exports into shipped points. Every spec here declares that +# line (`fit.convolution_line`) and opts in to user calibration (`user_calibrated`), so a calibration run's decode +# windows refit these lines per device. # # 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 diff --git a/invokeai/backend/working_memory/bounds.py b/invokeai/backend/working_memory/bounds.py index b1e7a64151c..d80c017a1c8 100644 --- a/invokeai/backend/working_memory/bounds.py +++ b/invokeai/backend/working_memory/bounds.py @@ -44,12 +44,20 @@ "vae.sd-miopen,native,windows-encode-fp16-1280x1280-scores-RX9060XT-demand_peak": 1.78, "vae.flux-miopen,native,windows-decode-bf16-512x512-scores-RX9060XT-demand_peak": 1.33, "vae.flux2-miopen,native,windows-decode-bf16-512x512-scores-RX9060XT-demand_peak": 1.33, + "denoise.flux2-cudnn,native-denoise-bf16-h3072-8100+77tok-RTX4090-demand_peak": 1.42, + "denoise.flux2-miopen,native,windows-denoise-bf16-h3072-8100+77tok-RX9060XT-demand_peak": 1.40, } ) """In-fit points, by `point_id`, 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.""" +instead. All are the native allocator, never expandable segments or `cudaMallocAsync`: + +- The VAE points are MIOpen 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. +- The FLUX.2 denoise points are the Klein 4B width at 1440px with a 77-token caption, 8177 tokens, on both cards: the + native allocator served them from 1676 MiB (RTX 4090) and 1879 MiB (RX 9060 XT), while the 512-token caption at + 8612 tokens took 2382 and 2577 MiB. The line has to cover the latter. Under `cudaMallocAsync` and expandable + segments the two lie on one line.""" def point_id(point: MeasuredPoint, spec: "EstimatorSpec") -> str: diff --git a/invokeai/backend/working_memory/data/denoise.json b/invokeai/backend/working_memory/data/denoise.json index cc2c2a064be..57ff04f2be6 100644 --- a/invokeai/backend/working_memory/data/denoise.json +++ b/invokeai/backend/working_memory/data/denoise.json @@ -104,6 +104,110 @@ "b_denoise_fp32": 134217728 }, "provenance": "Windows only, where expandable segments are Invoke's ROCm default. The Z-Image transformer forward's lines, bytes per attended token plus a base, per dtype. RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True, MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Measured by scripts/calibrate_z_image_working_memory.py on 2026-10-18: bf16, fp16 and fp32 at 512, 768, 1024, 1440 and 2048px with a 512-token caption, and at 1024 and 1440px with a 77-token one, 21 plain forwards on a random-init transformer of the real geometry (memory does not depend on weight values) with 2 main blocks and one refiner block each, one fresh process per point with the server's runtime, demand_peak (the largest of the driver, reserved and allocated peak) of the larger of a cold and a warm forward. Fitted by scripts/working_memory/build_shipped.py: working_memory/fit.py's upper envelope of the points below, plus 5%. 4 main blocks measured the same at 1024px bf16 (937 MiB). The base carries the ~255 MiB a process's first forward maps beyond its second (477 against 222 MiB at 512px bf16). The real 30-block Z-Image Turbo in a running server measured below this line: 686 MiB of demand at 4128 tokens in bf16, which it prices at 1066 MiB (calibration probe windows, kept as evidence and not shipped)." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "values": { + "k_denoise_bf16_h3072": 304528, + "b_denoise_bf16_h3072": 0, + "k_denoise_bf16_h4096": 406842, + "b_denoise_bf16_h4096": 0, + "k_denoise_bf16_h6144": 607199, + "b_denoise_bf16_h6144": 0 + }, + "unmeasured_floor": { + "k_denoise_bf16_h3072": 330300, + "b_denoise_bf16_h3072": 1073741824, + "k_denoise_bf16_h4096": 440401, + "b_denoise_bf16_h4096": 1073741824, + "k_denoise_bf16_h6144": 660601, + "b_denoise_bf16_h6144": 1073741824 + }, + "provenance": "The FLUX.2 transformer forward's lines, bytes per attended token and sample plus a base, one per width (3072, 4096, 6144), bf16. RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA. Measured by scripts/calibrate_flux2_denoise_working_memory.py on 2026-10-18 per width: 512, 768, 1024, 1440 and 2048px with a 512-token caption, 1024 and 1440px with a 77-token one, and at 1024px one and three 1024px reference images and a batch of two, and with --mode large long sequences: 2048px with one 2024px reference (33025 tokens), 1536px with one (25857) and 1328px with three 1328px references, which the node resizes to 1024px each (19689); plain forwards on a random-init transformer of the model loader's geometry (memory does not depend on weight values) with 2 double-stream and 2 single-stream blocks, one fresh process per point with the server's runtime, demand_peak (the largest of the driver, reserved and allocated peak) of the larger of a cold and a warm forward. Fitted by scripts/working_memory/build_shipped.py: working_memory/fit.py's upper envelope of the points below, plus 5%. Reference images and the batch lie on the line of the plain forwards. 4 blocks per stream measured the same as 2 at 1024px at every width on this card (native allocator). The real models in a running server (calibration probe windows, kept as evidence and not shipped) demanded 0.96 to 0.99 times the 2-block point at the same tokens for an fp8 Klein 4B at 1024 to 2048px, and 0.96 to 0.97 for an fp8 Klein 9B, some of its windows partially resident. The Klein 4B line holds its 1440px forward with a 77-token caption at 1.42x (`bounds.UPPER_BOUND_EXCEPTIONS`): the native allocator served it from 1676 MiB, the 512-token caption at 5% more tokens from 2382 MiB. Where this entry prices a platform nothing is measured on (MPS, XPU, the CPU, or cuDNN with expandable segments), each value is floored at the legacy constants the node reserved by hand, 0.42 MiB per token at width 4096 scaled by the width (330300, 440401 and 660601 bytes) and a 1 GiB base: the fit describes the card its points were measured on, not the platforms it stands in for." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "values": { + "k_denoise_bf16_h3072": 202699, + "b_denoise_bf16_h3072": 0, + "k_denoise_bf16_h4096": 267429, + "b_denoise_bf16_h4096": 11534336, + "k_denoise_bf16_h6144": 359718, + "b_denoise_bf16_h6144": 65011712 + }, + "provenance": "The FLUX.2 transformer forward's lines, bytes per attended token and sample plus a base, one per width (3072, 4096, 6144), bf16. RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync, fused SDPA. Measured by scripts/calibrate_flux2_denoise_working_memory.py on 2026-10-18 per width: 512, 768, 1024, 1440 and 2048px with a 512-token caption, 1024 and 1440px with a 77-token one, and at 1024px one and three 1024px reference images and a batch of two, and with --mode large long sequences: 2048px with one 2024px reference (33025 tokens), 1536px with one (25857) and 1328px with three 1328px references, which the node resizes to 1024px each (19689); plain forwards on a random-init transformer of the model loader's geometry (memory does not depend on weight values) with 2 double-stream and 2 single-stream blocks, one fresh process per point with the server's runtime, demand_peak (the largest of the driver, reserved and allocated peak) of the larger of a cold and a warm forward. Fitted by scripts/working_memory/build_shipped.py: working_memory/fit.py's upper envelope of the points below, plus 5%. Reference images and the batch lie on the line of the plain forwards. The driver's pool demands 33 to 41% less per token than the native allocator on this card." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native" + }, + "values": { + "k_denoise_bf16_h3072": 330300, + "b_denoise_bf16_h3072": 1073741824, + "k_denoise_bf16_h4096": 440401, + "b_denoise_bf16_h4096": 1073741824, + "k_denoise_bf16_h6144": 660601, + "b_denoise_bf16_h6144": 1073741824 + }, + "legacy": true, + "provenance": "Legacy, not a fit, for every OS but Windows, which has its own entry: no fresh-process point measures ROCm with the native allocator yet. The constants the FLUX.2 denoise node reserved by hand: 0.42 MiB per attended token and sample at the Klein 9B width (4096), scaled linearly by the transformer's width and truncated to whole bytes per width (330300 at 3072, 440401 at 4096, 660601 at 6144), and a 1 GiB base. The 0.42 bounded the slope of peak reserved memory between 4608 and 9216 tokens on the Klein 9B geometry in bf16, 0.386 MiB per token on an RTX 4090 (torch 2.7.1) and 0.407 on an RX 9070 XT (gfx1201, torch 2.10); per width 0.291, 0.386 and 0.555 MiB on CUDA and 0.315, 0.407 and 0.589 on ROCm, each point in a fresh process on a random-init transformer with reduced blocks." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "values": { + "k_denoise_bf16_h3072": 305267, + "b_denoise_bf16_h3072": 245366784, + "k_denoise_bf16_h4096": 404611, + "b_denoise_bf16_h4096": 238026752, + "k_denoise_bf16_h6144": 601721, + "b_denoise_bf16_h6144": 232783872 + }, + "unmeasured_floor": { + "k_denoise_bf16_h3072": 330300, + "b_denoise_bf16_h3072": 1073741824, + "k_denoise_bf16_h4096": 440401, + "b_denoise_bf16_h4096": 1073741824, + "k_denoise_bf16_h6144": 660601, + "b_denoise_bf16_h6144": 1073741824 + }, + "provenance": "Windows only; every other OS keeps its own entry. The FLUX.2 transformer forward's lines, bytes per attended token and sample plus a base, one per width (3072, 4096, 6144), bf16. RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Measured by scripts/calibrate_flux2_denoise_working_memory.py on 2026-10-18 per width: 512, 768, 1024, 1440 and 2048px with a 512-token caption, 1024 and 1440px with a 77-token one, and at 1024px one and three 1024px reference images and a batch of two, and with --mode large long sequences: 2048px with one 2024px reference (33025 tokens), 1536px with one (25857) and 1328px with three 1328px references, which the node resizes to 1024px each (19689); plain forwards on a random-init transformer of the model loader's geometry (memory does not depend on weight values) with 2 double-stream and 2 single-stream blocks, one fresh process per point with the server's runtime, demand_peak (the largest of the driver, reserved and allocated peak) of the larger of a cold and a warm forward. Fitted by scripts/working_memory/build_shipped.py: working_memory/fit.py's upper envelope of the points below, plus 5%. Reference images and the batch lie on the line of the plain forwards. 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB). [dev]'s line comes from 512 to 1440px, one 1024px reference included: at 2048px, with three references and in the large grid it does not fit 16 GB beside its weights; the Klein 4B and 9B lines are measured up to 33025 tokens where they fit. The base carries the ~245 MiB a process's first forward maps beyond its second (565 against 320 MiB at 512px, width 3072). The Klein 4B line holds its 1440px forward with a 77-token caption at 1.40x (`bounds.UPPER_BOUND_EXCEPTIONS`): the native allocator served it from 1879 MiB, the 512-token caption at 5% more tokens from 2577 MiB. Where this entry prices a platform nothing is measured on (MIOpen under hipMallocAsync on Windows), each value is floored at the legacy constants the node reserved by hand, 0.42 MiB per token at width 4096 scaled by the width (330300, 440401 and 660601 bytes) and a 1 GiB base: the fit describes the card its points were measured on, not the platforms it stands in for." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "values": { + "k_denoise_bf16_h3072": 181485, + "b_denoise_bf16_h3072": 292552704, + "k_denoise_bf16_h4096": 241980, + "b_denoise_bf16_h4096": 283115520, + "k_denoise_bf16_h6144": 362969, + "b_denoise_bf16_h6144": 284164096 + }, + "provenance": "Windows only, where expandable segments are Invoke's ROCm default. The FLUX.2 transformer forward's lines, bytes per attended token and sample plus a base, one per width (3072, 4096, 6144), bf16. RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True, MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Measured by scripts/calibrate_flux2_denoise_working_memory.py on 2026-10-18 per width: 512, 768, 1024, 1440 and 2048px with a 512-token caption, 1024 and 1440px with a 77-token one, and at 1024px one and three 1024px reference images and a batch of two, and with --mode large long sequences: 2048px with one 2024px reference (33025 tokens), 1536px with one (25857) and 1328px with three 1328px references, which the node resizes to 1024px each (19689); plain forwards on a random-init transformer of the model loader's geometry (memory does not depend on weight values) with 2 double-stream and 2 single-stream blocks, one fresh process per point with the server's runtime, demand_peak (the largest of the driver, reserved and allocated peak) of the larger of a cold and a warm forward. Fitted by scripts/working_memory/build_shipped.py: working_memory/fit.py's upper envelope of the points below, plus 5%. Reference images and the batch lie on the line of the plain forwards. 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB). [dev]'s line comes from 512 to 1440px, one 1024px reference included: at 2048px, with three references and in the large grid it does not fit 16 GB beside its weights; the Klein 4B and 9B lines are measured up to 33025 tokens where they fit. The base carries the ~245 MiB a process's first forward maps beyond its second (453 against 224 MiB at 512px, width 3072). The real models in a running server (calibration probe windows, kept as evidence and not shipped) demanded 0.81 to 0.95 times the 2-block point at the same tokens for a bf16 Klein 4B, and 0.71 to 0.77 for an fp8 Klein 9B after its 576 MiB fp8 cast term: the 2-block points bound the real models, the extra blocks' 5 to 7% included." } ], "points": [ @@ -2248,6 +2352,3961 @@ "method": "demand_peak", "device": "RX 9060 XT (gfx1200)", "provenance": "scripts/calibrate_z_image_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init Z-Image transformer with the real geometry (memory does not depend on weight values), 2 main blocks and one refiner block each; 4 main blocks measured the same at 1024px bf16 on this card (937 MiB, under expandable segments). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 373293056, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 799014912, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1317011456, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2497708032, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4884267008, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1207959552, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1757413376, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2506096640, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4884267008, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2606759936, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 494927872, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1046478848, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1738539008, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3319791616, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 6505365504, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1616904192, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2791309312, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3338665984, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 6505365504, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3433037824, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 742391808, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1564475392, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2585788416, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4940890112, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 9718202368, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2397044736, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4185915392, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4974444544, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 9718202368, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5140119552, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 285212672, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 520093696, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 822083584, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1476395008, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2919235584, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 805306368, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1392508928, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1493172224, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2919235584, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1627389952, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 385875968, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 721420288, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1090519040, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1946157056, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3707764736, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1073741824, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1828716544, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1979711488, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3707764736, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2130706432, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 587202560, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1023410176, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1627389952, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2919235584, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5486149632, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1442840576, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2734686208, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2952790016, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5486149632, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3070230528, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 475398144, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 685113344, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 936771584, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1649803264, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2991980544, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 999686144, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1691746304, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1670774784, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2991980544, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1649803264, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 580255744, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 831913984, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1188429824, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2132148224, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3914727424, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1230372864, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2153119744, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2153119744, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3914727424, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2090205184, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 727056384, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1104543744, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1649803264, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3033923584, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1712717824, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3096838144, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3075866624, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3012952064, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 592838656, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1035337728, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1538654208, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2702573568, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5049286656, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1446379520, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1970667520, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2713059328, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5049286656, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2824208384, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 722862080, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1289093120, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1976958976, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3509977088, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 6636830720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1834352640, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3025534976, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3528851456, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 16896, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 6636830720, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3671457792, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 1024, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 1536, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 974520320, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 2304, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 2816, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 1784020992, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2824208384, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 8612, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5156241408, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 4173, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 2612396032, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 8100, + "reference_tokens": 0, + "text_tokens": 77, + "tokens": 8177, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4399169536, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 4096, + "text_tokens": 512, + "tokens": 8704, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5189795840, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 4096, + "reference_tokens": 0, + "text_tokens": 512, + "tokens": 4608, + "batch_size": 2, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5357568000, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 9412018176, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 7381975040, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5635047424, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 12549357568, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 10009706496, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 7453278208, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 14952693760, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "native" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 11358175232, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, no allocator configuration (the native allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 6375342080, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4966055936, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3422552064, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 8422162432, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5939134464, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4529848320, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 11374952448, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 8371830784, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "cudnn", + "allocator": "cudaMallocAsync" + }, + "features": { + "hidden_size": 6144, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 6425673728, + "method": "demand_peak", + "device": "RTX 4090", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RTX 4090, torch 2.13.0+cu130, Windows, PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync (the cudaMallocAsync allocator), fused SDPA; AOTriton and the ROCm SDPA guard are ROCm's and do not apply. Random-init FLUX.2 transformer of width 6144 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured the same as 2 at 1024px at every width on this card (1256, 1658 and 2466 MiB, native allocator). demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5613420544, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4439015424, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 3453353984, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 7354056704, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5823135744, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "expandable", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 4480958464, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (expandable segments), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 16384, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 33025, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 9660923904, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 7605714944, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 3072, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 5957353472, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 3072 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 9216, + "reference_tokens": 16129, + "text_tokens": 512, + "tokens": 25857, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 10063577088, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." + }, + { + "estimator": "denoise.flux2", + "version": 2, + "platform": { + "conv_backend": "miopen", + "allocator": "native", + "os": "windows" + }, + "features": { + "hidden_size": 4096, + "image_tokens": 6889, + "reference_tokens": 12288, + "text_tokens": 512, + "tokens": 19689, + "batch_size": 1, + "dtype": "bf16", + "has_attention_mask": false, + "regional_bias_bytes": 0, + "score_matrix_bytes": 0, + "lora_count": 0, + "dequant_transient_bytes": 0, + "fp8_cast_transient_bytes": 0 + }, + "measured_bytes": 7813332992, + "method": "demand_peak", + "device": "RX 9060 XT (gfx1200)", + "provenance": "scripts/calibrate_flux2_denoise_working_memory.py, 2026-10-18: RX 9060 XT (gfx1200), torch 2.13.0+rocm10.0.0 (HIP 7.15.26333), Windows, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False (the native allocator), MIOPEN_FIND_MODE=FAST, TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 (fused SDPA), ROCm SDPA guard installed. Random-init FLUX.2 transformer of width 4096 with the model loader's geometry (memory does not depend on weight values), 2 double-stream and 2 single-stream blocks; 4 blocks per stream measured 5 to 7% more than 2 at 1024px on this card (953 against 893 MiB at width 3072, 1193 against 1133 MiB at 4096, expandable segments; [dev] with 4 blocks does not fit 16 GB), which the 5% margin and the real models' lower demand cover. demand_peak: the largest of the driver, reserved and allocated peak over the baseline, the larger of a cold and a warm forward, one fresh process per point with the server's runtime; no failed allocation, allocator retry or spill." } ] } diff --git a/invokeai/backend/working_memory/fit.py b/invokeai/backend/working_memory/fit.py index 54f065b33af..691517a84fb 100644 --- a/invokeai/backend/working_memory/fit.py +++ b/invokeai/backend/working_memory/fit.py @@ -3,9 +3,9 @@ An estimator's fitted terms price a line per operation and dtype: `k_` bytes per unit of the line's size plus `b_` bytes, the base a process pays whatever the size. Its `EstimatorSpec.line` says which line an operation's features price on, their size on it, and whether a measurement of them can join its fit (`Line`). The AutoencoderKL -estimators price their convolutions on a line in the output's pixels (`convolution_line`); the Z-Image denoise prices -its forward on one in attended tokens. Each carried pair is the upper envelope of the fresh-process points it is -resolved for (see `upper_envelope`), plus `SHIPPED_MARGIN`, raised where the line's other points need it +estimators price their convolutions on a line in the output's pixels (`convolution_line`); the Z-Image and FLUX.2 denoise +price their forwards on ones in attended tokens. Each carried pair is the upper envelope of the fresh-process points it +is resolved for (see `upper_envelope`), plus `SHIPPED_MARGIN`, raised where the line's other points need it (`raised_pair`). 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 `in_fit` (not measured out of regime) and its line admits it. @@ -43,7 +43,7 @@ from typing import TYPE_CHECKING, Final, NamedTuple, Optional from invokeai.backend.working_memory.coefficients import CoefficientEntry, MeasuredPoint, WorkingMemoryData, find_entry -from invokeai.backend.working_memory.estimate import FeatureValue +from invokeai.backend.working_memory.estimate import FeatureValue, WorkingMemoryTerm if TYPE_CHECKING: from invokeai.backend.working_memory.spec import EstimatorSpec @@ -88,6 +88,25 @@ class Line(NamedTuple): features that name no line.""" +def off_line_reasons( + terms: Callable[[Mapping[str, FeatureValue], Mapping[str, float]], Iterable[WorkingMemoryTerm]], + features: Mapping[str, FeatureValue], + coefficients: Iterable[str], +) -> list[str]: + """What puts an operation off the line its fitted terms price: an attention mask, which narrows the kernels SDPA can + pick (``has_attention_mask``), and each analytic term of `terms` that is not zero, as ``=``. Empty for + an operation whose demand is the line's alone. The terms are priced with every coefficient at zero, so only what + the operation adds on top of the line remains; a term added to an estimator takes the operations it prices out of + the fit by itself. The estimators label their points with the reasons in this order.""" + unpriced = dict.fromkeys(coefficients, 0.0) + reasons = ["attention_mask"] if features.get("has_attention_mask") else [] + return reasons + [ + f"{term.name}={int(term.bytes)}" + for term in terms(features, unpriced) + if term.coefficient is None and term.bytes + ] + + def convolution_line(features: Mapping[str, FeatureValue]) -> Optional[Line]: """The AutoencoderKL convolution line ``_``, in output pixels: every VAE estimator's. diff --git a/invokeai/backend/working_memory/measurement.py b/invokeai/backend/working_memory/measurement.py index cdf4a856923..b80cda6c8dc 100644 --- a/invokeai/backend/working_memory/measurement.py +++ b/invokeai/backend/working_memory/measurement.py @@ -21,11 +21,12 @@ Two kinds of window take these definitions. The in-server probe's (`probe`) follow an operation through co-locks, nested windows and releases, in segments, and have reasons of their own. A *fresh window* (`measure`) is one operation alone: the calibration scripts measure their points that way, each in a fresh process with the server's runtime -(`invokeai.app.run_app.apply_server_runtime`). +(`invokeai.app.run_app.apply_server_runtime`). Such a point is a cold and a warm window of the same operation +(`measure_cold_and_warm`), and its bytes are the larger of the two (`measured_bytes_and_method`). """ import time -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from dataclasses import dataclass from typing import Final, Literal, Optional @@ -181,3 +182,42 @@ def measure(device: torch.device, operation: Callable[[], object]) -> FreshWindo return fresh_window(first, final, oom=oom, spill=spill) finally: device_counters.close() + + +FreshPointMethod = Literal["demand_peak", "reserved_peak"] +"""The `MeasuredPoint.method` of a fresh-process point.""" + + +def measure_cold_and_warm(device: torch.device, operation: Callable[[], object]) -> tuple[FreshWindow, ...]: + """A fresh-process point's windows: `operation` measured twice (`measure`), cold and then warm. + + The first run after a load is a real peak in a server (kernel modules, library workspaces), and so is a later one, + which can demand more: a warm Z-Image forward under `cudaMallocAsync` read 800 MiB against its cold run's 720. A + cold run that ran out of memory ends the point, whose warm run would measure what the failure left behind. + """ + cold = measure(device, operation) + if "oom" in cold.reasons: + return (cold,) + return (cold, measure(device, operation)) + + +def point_reasons(windows: Sequence[FreshWindow]) -> list[ReadingReason]: + """Why a fresh-process point is invalid: its windows' reasons, each once, in order. Empty for a valid point.""" + return list(dict.fromkeys(reason for window in windows for reason in window.reasons)) + + +def measured_bytes_and_method(windows: Sequence[FreshWindow]) -> tuple[int, FreshPointMethod]: + """A valid fresh-process point's bytes and `MeasuredPoint.method`, from its windows (`measure_cold_and_warm`). + + The largest of each window's figure: its demand where it read the driver, otherwise the growth of torch's counters + alone. A window without the driver's figure does not discard another's, which on ROCm maps a few hundred MiB above + torch's reserve on a first forward. The method names what the largest was read from: `demand_peak` for a demand, + `reserved_peak` for torch's counters. + """ + figures: list[tuple[int, FreshPointMethod]] = [] + for window in windows: + if window.driver is not None and window.demand is not None: + figures.append((window.demand, "demand_peak")) + else: + figures += [(delta, "reserved_peak") for delta in (window.reserved, window.allocated) if delta is not None] + return max(figures, key=lambda figure: figure[0]) diff --git a/invokeai/backend/working_memory/probe.py b/invokeai/backend/working_memory/probe.py index 55cff8d4838..5f75062d8d2 100644 --- a/invokeai/backend/working_memory/probe.py +++ b/invokeai/backend/working_memory/probe.py @@ -61,6 +61,7 @@ from contextlib import contextmanager from typing import TYPE_CHECKING, Final, Literal, Optional +import torch from pydantic import BaseModel, ConfigDict from invokeai.app.services.session_processor.session_processor_common import CanceledException @@ -595,6 +596,17 @@ def current_scope() -> Optional[ProbeScope]: return _current_scope.get() +def lock_reservation(reservation_bytes: int, device: torch.device) -> tuple[Optional[ProbeScope], int]: + """What a lock on `device` keeps free for an estimate of `reservation_bytes`, and the calibration scope it locks + under: inside a scope open on this thread and on a GPU, that scope and the calibration reservation + (`calibration_reservation`); otherwise None and the estimate's bytes. The model loader applies it to a measured + lock, and a node to every lock beside one that must not use up the window's headroom.""" + scope = current_scope() + if scope is None or device.type == "cpu": + return None, reservation_bytes + return scope, calibration_reservation(reservation_bytes) + + class _EmptyCacheObserver: """Turns `TorchDevice.empty_cache` into segment boundaries of the open windows.""" diff --git a/invokeai/backend/working_memory/spec.py b/invokeai/backend/working_memory/spec.py index 273088237f2..2afb662af86 100644 --- a/invokeai/backend/working_memory/spec.py +++ b/invokeai/backend/working_memory/spec.py @@ -19,6 +19,7 @@ "invokeai.backend.moss_image.working_memory", "invokeai.backend.util.vae_working_memory", "invokeai.backend.z_image.denoise_working_memory", + "invokeai.backend.flux2.denoise_working_memory", ) """Every module that registers an `EstimatorSpec`. The registry holds only the specs of modules imported so far, so a caller that needs all of them imports these (`import_estimator_modules`); `tests/backend/working_memory/` fails on a @@ -46,7 +47,7 @@ class EstimatorSpec: line: LineFunction = field(compare=False, repr=False) """The line an operation's features price on, their size on it, and whether a measurement of them fits it: the AutoencoderKL convolution line in output pixels for every VAE estimator (`fit.convolution_line`), one in attended - tokens for the Z-Image denoise. Declared by every estimator, so a new one cannot inherit another's line, and with it + tokens for each denoise estimator. Declared by every estimator, so a new one cannot inherit another's line, and with it which of its measurements join a fit.""" user_calibrated: bool = False """Whether a user's calibration moves this estimator: its windows fit user lines, its estimates read them, the diff --git a/invokeai/backend/z_image/denoise_working_memory.py b/invokeai/backend/z_image/denoise_working_memory.py index 2dfe39d5775..23a6ccbb293 100644 --- a/invokeai/backend/z_image/denoise_working_memory.py +++ b/invokeai/backend/z_image/denoise_working_memory.py @@ -61,7 +61,6 @@ """ from collections.abc import Mapping -from types import MappingProxyType from typing import Final, Optional, cast import torch @@ -71,7 +70,7 @@ from invokeai.backend.util.attention import sdpa_score_matrix_bytes from invokeai.backend.util.fp8 import peak_fp8_cast_transient_bytes from invokeai.backend.working_memory.estimate import FeatureValue, WorkingMemoryEstimate, WorkingMemoryTerm -from invokeai.backend.working_memory.fit import Line +from invokeai.backend.working_memory.fit import Line, off_line_reasons from invokeai.backend.working_memory.platform import platform_for from invokeai.backend.working_memory.spec import EstimatorSpec, register @@ -86,9 +85,6 @@ """The dtypes `TorchDevice.choose_bfloat16_safe_dtype` runs the transformer in.""" _ELEMENT_SIZES: Final[Mapping[str, int]] = {"bf16": 2, "fp16": 2, "fp32": 4} _COEFFICIENTS: Final = frozenset(f"{part}_denoise_{dtype}" for part in ("k", "b") for dtype in _ELEMENT_SIZES) -_UNPRICED: Final[Mapping[str, float]] = MappingProxyType(dict.fromkeys(_COEFFICIENTS, 0.0)) -"""Coefficient values that price the fitted terms at nothing: what remains of a forward's terms is what it adds on top -of its line.""" def padded_tokens(tokens: int) -> int: @@ -117,9 +113,7 @@ def _line(features: Mapping[str, FeatureValue]) -> Optional[Line]: dtype, tokens = features.get("dtype"), features.get("tokens") if not isinstance(dtype, str) or dtype not in _ELEMENT_SIZES or not isinstance(tokens, int): return None - on_top = [term for term in _terms(features, _UNPRICED) if term.coefficient is None and term.bytes] - off_line = ["attention_mask"] if features.get("has_attention_mask") else [] - off_line += [f"{term.name}={int(term.bytes)}" for term in on_top] + off_line = off_line_reasons(_terms, features, _COEFFICIENTS) shape = f"{features.get('image_tokens')}+{features.get('text_tokens')}tok" return Line(f"denoise_{dtype}", tokens, not off_line, "-".join(("denoise", dtype, shape, *off_line))) diff --git a/scripts/calibrate_flux2_denoise_working_memory.py b/scripts/calibrate_flux2_denoise_working_memory.py new file mode 100644 index 00000000000..14e80c659b8 --- /dev/null +++ b/scripts/calibrate_flux2_denoise_working_memory.py @@ -0,0 +1,644 @@ +"""Measure the working memory of one FLUX.2 transformer forward, as shipped-data points for `denoise.flux2`. + +What a point measures +--------------------- +The demand of one transformer forward, measured as a fresh window (`invokeai/backend/working_memory/measurement.py`, +whose definitions the in-server probe's windows share). With the model and its inputs resident: synchronize, release the +allocator's cached blocks, read the baseline and reset the peaks; run the forward; the demand is then the largest growth +of three counters over the baseline: the driver's figure for this process, torch's reserved peak and its allocated peak. +A forward whose allocations failed, or only succeeded after the allocator released its cache and retried (a squeezed +peak), or after which Windows keeps more than `measurement.SPILL_TOLERANCE_BYTES` of the process in shared memory, is +invalid: it is recorded as such, not as a point. + +Every point runs in a fresh process (`scripts/working_memory/fresh_points.py`, which the calibration scripts share), +which first applies the server's runtime (`apply_server_runtime` in `invokeai/app/run_app.py`): the `MIOPEN_FIND_MODE` +default, the integrated-GPU check, the allocator, the AOTriton decision and the monkeypatches, the ROCm SDPA guard among +them, in the server's order. Its settings are those of a server without a config file, except the allocator +configuration: `--alloc-conf`, or without one the platform default the server uses (expandable segments for ROCm on +Windows, torch's native allocator elsewhere). This script's own process runs the integrated-GPU check once before the +first point: the measuring processes inherit the `HIP_VISIBLE_DEVICES` it sets, which their own check keeps, as the +server keeps an exported one. + +The forward then runs twice, cold and warm, and the point takes the larger demand (`measure_cold_and_warm` and +`measured_bytes_and_method` in `measurement.py`): a `demand_peak` where both forwards read the driver's figure, else a +`reserved_peak` of torch's counters alone. + +The transformer is `Flux2Transformer2DModel` at the geometry the model loader builds for a variant's width (heads of +128, the variant's caption width, guidance embeddings) with random bf16 weights, because memory depends on shapes, not +on weight values. It has 2 double-stream and 2 single-stream blocks: a no-grad forward frees each block's +intermediates, which `--extra-blocks 4` re-checks at 1024px per width. The inputs are what the node hands it: the +image's and the reference images' tokens on one stream, a caption, the timestep and the guidance, a sample of each per +batch entry. A point carries the features of `estimate_flux2_denoise_working_memory`, taken in the measuring process +after the runtime setup, so its `score_matrix_bytes` reflects the attention kernel this build really runs. + +Each point is priced before it runs: the estimate for it plus its weights. It is skipped when that exceeds free device +memory, unless `--force`. An oversized allocation does not always fail: where the driver backs it with system RAM +(NVIDIA's sysmem fallback, AMD on Windows) it pages, which can stall the whole machine. `--dry-run` prints that price +per point as a CPU prices it: a CPU is an unmeasured platform, so that is cuDNN's native-allocator entry floored at the +legacy constants (`unmeasured_floor`), above what the point's measured platform reserves wherever the fit lies below +them. + +Modes +----- +- `fit` (default): the fit grid, per `--widths` (3072, 4096 and 6144: Klein 4B, Klein 9B and [dev]). The `--sizes` + (512, 768, 1024, 1440, 2048 px) run with a `--text-tokens` caption (512). Those among `--short-text-sizes` (1024, + 1440) also run with a `--short-text-tokens` caption (77), for the text slope. At 1024px, `--references` (1 and 3) runs + with that many `--reference-px` (1024) reference images, and `--batches` (2) with that many samples. `--extra-blocks + N` adds one forward with N blocks per stream at 1024px per width. +- `large`: long sequences, per `--widths` (default 3072 and 4096; [dev] fits 24 GB only below 33025 tokens): 2048px + with one 2024px reference, 33025 tokens with a 512-token caption; 1536px with one 2024px reference, 25857 tokens; and + 1328px with three 1328px references, which the node resizes to 1024px each, 19689 tokens. The node caps neither the + image's side nor the number of references; longer sequences price on the line extrapolated. +- `math`: SDPA forced onto the math kernel at 1024px, next to the default kernel, per width: what a materialized score + matrix costs per element, against what the estimate charges for it. + +A reference image's tokens are what `Flux2RefImageExtension` concatenates for it: resized as it resizes them (one +reference to at most 2024x2024 pixels, each of several to at most 1024x1024), encoded at 1/8 of the pixels, padded to +whole 2x2 patches. A 1024px reference is 4096 tokens, a 2024px one 16129. + +The fit grid's and the large grid's points are fit points (`"fit": true`): the plain forwards, reference images and +batches included, which the estimator's line admits (`denoise_working_memory._line`). The checks' points, the +extra-blocks forward included, test analytic terms or assumptions rather than the line: they are marked `"fit": false`, +and their `in_fit` is false. A fit point is `in_fit` only where the line admits its features: on a build whose SDPA +kernel materializes the score matrix, the fit grid's forwards carry a score term on top of the line, so they measure +that term rather than the line, as a check does. + +Output +------ +Every measured point is appended to `--out` as one JSON object per line, the point file +`scripts/working_memory/build_shipped.py --points` reads. It carries the fields of a `MeasuredPoint` +(`invokeai/backend/working_memory/coefficients.py`): + +- `estimator`, `version` and `features`, the estimate's own; +- `platform`, as `platform_for` gives it, OS included; the builder names the OS only where the data needs it; +- `measured_bytes` (the demand), `method`, `device` and `in_fit`; +- `provenance`, which names the GPU, the torch build, the OS, the allocator configuration, on ROCm `MIOPEN_FIND_MODE`, + the AOTriton switch and the SDPA guard, the block count and the date. + +The other keys are the script's, which the builder ignores: `fit`, `point` (the grid entry) and `raw` (each forward's +counter deltas, the prediction and the runtime facts). An invalid, skipped or failed forward is no point: it shows in +the table on stdout, with its reasons, and stays out of the file. + +Usage +----- +RTX 4090 (CUDA): the native allocator, then `cudaMallocAsync`; the math check once. + + python scripts/calibrate_flux2_denoise_working_memory.py --out 4090-native.jsonl --extra-blocks 4 + python scripts/calibrate_flux2_denoise_working_memory.py --out 4090-async.jsonl --alloc-conf backend:cudaMallocAsync + python scripts/calibrate_flux2_denoise_working_memory.py --out 4090-checks.jsonl --mode math + python scripts/calibrate_flux2_denoise_working_memory.py --out 4090-native-large.jsonl --mode large + python scripts/calibrate_flux2_denoise_working_memory.py --out 4090-async-large.jsonl --mode large \ + --alloc-conf backend:cudaMallocAsync + +RX 9060 XT (ROCm on Windows): the server's default expandable segments, then the native allocator; the math check once. + + python scripts/calibrate_flux2_denoise_working_memory.py --out 9060xt-expandable.jsonl --extra-blocks 4 + python scripts/calibrate_flux2_denoise_working_memory.py --out 9060xt-native.jsonl --alloc-conf expandable_segments:False + python scripts/calibrate_flux2_denoise_working_memory.py --out 9060xt-checks.jsonl --mode math + +`--dry-run` prints the planned points without touching a device, each priced as a CPU prices it, e.g. + + python scripts/calibrate_flux2_denoise_working_memory.py --dry-run --mode large +""" + +import argparse +import dataclasses +import datetime +import sys +from collections.abc import Mapping, Sequence +from contextlib import nullcontext +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Any, Literal + +# None of these imports torch: a measuring process has to configure the allocator before torch is imported. +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR # noqa: E402 +from scripts.working_memory import fresh_points # noqa: E402 + +if TYPE_CHECKING: + import torch + + from invokeai.backend.working_memory.estimate import WorkingMemoryEstimate + +GIB = 2**30 +MIB = 2**20 + +PATCH_SIZE = 2 +"""FLUX.2 packs 2x2 latent patches into one token.""" +LATENT_CHANNELS = 128 +"""A packed token's channels: 32 latent channels times the 2x2 patch.""" +HEAD_DIM = 128 +CONTEXT_DIMS = {3072: 7680, 4096: 12288, 6144: 15360} +"""The caption width per transformer width: 3x the text encoder's hidden size (Qwen3-4B, Qwen3-8B, Mistral Small 3.1), +as `model_manager/configs/flux2_variant.py` declares them.""" +CHECK_PX = 1024 +"""The size the reference, batch and extra-blocks points and the checks run at.""" +REFERENCE_PX = 1024 +"""The fit grid's reference images' side, `--reference-px`'s default.""" +LARGE_GRID = ((2048, 1, 2024), (1536, 1, 2024), (1328, 3, 1328)) +"""The large grid's forwards: image side, reference count and reference side.""" +LARGE_WIDTHS = (3072, 4096) +DTYPE = "bfloat16" +"""The node runs the transformer in bf16 only.""" +TIMESTEP = 0.5 +GUIDANCE = 4.0 + +LOGGER_NAME = "calibrate_flux2_denoise_working_memory" + +Kind = Literal["fit", "blocks", "baseline", "math"] +Mode = Literal["fit", "large", "math"] + + +@dataclass(frozen=True) +class Point: + """One transformer forward to measure, in a process of its own.""" + + kind: Kind + """`fit` for a point of the fit grid; otherwise the check it serves: another block count (`blocks`), the + default-kernel forward a check compares with (`baseline`) or the forced math kernel (`math`).""" + hidden_size: int + px: int + text_tokens: int + references: int = 0 + """How many reference images of `reference_px` square the forward attends to.""" + reference_px: int = REFERENCE_PX + batch: int = 1 + blocks: int = 2 + """Double-stream and single-stream blocks, each.""" + force_math: bool = False + + @property + def fit(self) -> bool: + return self.kind == "fit" + + +def image_tokens(px: int) -> int: + """A square image's tokens: its latent at 1/8 of the pixels, in 2x2 patches.""" + side = px // LATENT_SCALE_FACTOR // PATCH_SIZE + return side * side + + +def reference_tokens(point: Point) -> int: + """What `Flux2RefImageExtension` concatenates onto the image stream for the point's references: each resized as it + resizes them, its latent at 1/8 of the pixels padded to whole 2x2 patches.""" + if not point.references: + return 0 + from PIL import Image + + from invokeai.backend.flux2.ref_image_extension import ( + MAX_PIXELS_MULTI_REF, + MAX_PIXELS_SINGLE_REF, + resize_image_to_max_pixels, + ) + + cap = MAX_PIXELS_SINGLE_REF if point.references == 1 else MAX_PIXELS_MULTI_REF + width, height = resize_image_to_max_pixels(Image.new("L", (point.reference_px, point.reference_px)), cap).size + rows, columns = (-(-(side // LATENT_SCALE_FACTOR) // PATCH_SIZE) for side in (height, width)) + return point.references * rows * columns + + +def plan( + mode: Mode, + *, + widths: Sequence[int], + sizes: Sequence[int], + text_tokens: int, + short_text_tokens: int, + short_text_sizes: Sequence[int], + references: Sequence[int], + batches: Sequence[int], + blocks: int, + extra_blocks: int, + reference_px: int = REFERENCE_PX, +) -> list[Point]: + """The forwards a run measures, in order: per width, the fit grid, the large grid or the math check's pair of + forwards.""" + points: list[Point] = [] + for width in widths: + if mode == "large": + points += [ + Point("fit", width, px, text_tokens, references=n, reference_px=ref_px, blocks=blocks) + for px, n, ref_px in LARGE_GRID + ] + continue + if mode == "math": + points.append(Point("baseline", width, CHECK_PX, text_tokens, blocks=blocks)) + points.append(Point("math", width, CHECK_PX, text_tokens, blocks=blocks, force_math=True)) + continue + points += [Point("fit", width, px, text_tokens, blocks=blocks) for px in sizes] + points += [Point("fit", width, px, short_text_tokens, blocks=blocks) for px in sizes if px in short_text_sizes] + points += [ + Point("fit", width, CHECK_PX, text_tokens, references=n, reference_px=reference_px, blocks=blocks) + for n in references + if n + ] + points += [Point("fit", width, CHECK_PX, text_tokens, batch=n, blocks=blocks) for n in batches if n != 1] + if extra_blocks: + points.append(Point("blocks", width, CHECK_PX, text_tokens, blocks=extra_blocks)) + return points + + +def provenance(point: Point, facts: Mapping[str, Any], date: str) -> str: + """A measured point's `provenance`: where and how it was measured.""" + regime = ", SDPA forced onto the math kernel" if point.force_math else "" + blocks = f"{point.blocks} double-stream and {point.blocks} single-stream block{'' if point.blocks == 1 else 's'}" + return ( + f"scripts/calibrate_flux2_denoise_working_memory.py ({date}): {fresh_points.runtime_provenance(facts)}. " + "Random-init FLUX.2 " + f"transformer of width {point.hidden_size} with the model loader's geometry, {blocks}{regime}; memory does not " + "depend on weight values. A fresh process with the server's runtime, the larger demand of a cold and a warm " + "forward; no failed allocation, allocator retry or spill." + ) + + +# Everything below imports torch. In a measuring process it runs only after `fresh_points.apply_server_runtime`. + + +def _estimate(point: Point, device: "torch.device") -> "WorkingMemoryEstimate": + """The estimate for the point's forward on `device`, priced in the kernel regime the caller holds.""" + import torch + + from invokeai.backend.flux2.denoise_working_memory import estimate_flux2_denoise_working_memory + + return estimate_flux2_denoise_working_memory( + # The point runs a randomly initialized dense model, which dequantizes and casts nothing per forward. + torch.nn.Module(), + hidden_size=point.hidden_size, + image_seq_len=image_tokens(point.px), + ref_image_seq_len=reference_tokens(point), + text_seq_len=point.text_tokens, + batch_size=point.batch, + lora_count=0, + # Measured whole on the device: nothing is streamed per forward. + weights_resident=True, + device=device, + dtype=getattr(torch, DTYPE), + ) + + +def _transformer(hidden_size: int, blocks: int) -> "torch.nn.Module": + """The FLUX.2 transformer of a width, as the model loader builds it, with `blocks` blocks per stream, on the meta + device.""" + import torch + from diffusers import Flux2Transformer2DModel + + with torch.device("meta"): + return Flux2Transformer2DModel( + in_channels=LATENT_CHANNELS, + out_channels=LATENT_CHANNELS, + num_layers=blocks, + num_single_layers=blocks, + attention_head_dim=HEAD_DIM, + num_attention_heads=hidden_size // HEAD_DIM, + joint_attention_dim=CONTEXT_DIMS[hidden_size], + patch_size=1, + ) + + +def _weight_bytes(transformer: "torch.nn.Module") -> int: + """A meta-device transformer's weights in bf16.""" + return sum(param.numel() for param in transformer.parameters()) * 2 + + +def _inputs(point: Point, device: "torch.device") -> dict[str, Any]: + """What the node hands the transformer per forward: the image's and the references' tokens on one stream, their + position ids, a caption, the timestep and the guidance, `point.batch` samples of each.""" + import torch + + from invokeai.backend.flux2.sampling_utils import generate_img_ids_flux2 + + dtype = getattr(torch, DTYPE) + latent = point.px // LATENT_SCALE_FACTOR + references = reference_tokens(point) + img_ids = generate_img_ids_flux2(h=latent, w=latent, batch_size=point.batch, device=device) + if references: + ref_ids = torch.zeros(point.batch, references, 4, device=device, dtype=torch.long) + img_ids = torch.cat([img_ids, ref_ids], dim=1) + txt_ids = torch.zeros(point.batch, point.text_tokens, 4, device=device, dtype=torch.long) + txt_ids[..., 3] = torch.arange(point.text_tokens, device=device, dtype=torch.long) + tokens = image_tokens(point.px) + references + return { + "hidden_states": torch.randn(point.batch, tokens, LATENT_CHANNELS, device=device, dtype=dtype), + "encoder_hidden_states": torch.randn( + point.batch, point.text_tokens, CONTEXT_DIMS[point.hidden_size], device=device, dtype=dtype + ), + "timestep": torch.full((point.batch,), TIMESTEP, device=device, dtype=dtype), + "img_ids": img_ids, + "txt_ids": txt_ids, + "guidance": torch.full((point.batch,), GUIDANCE, device=device, dtype=dtype), + "return_dict": False, + } + + +def _measure(point: Point, force: bool) -> dict[str, Any]: + """The output line of `point`, measured in this process after `fresh_points.apply_server_runtime`.""" + import torch + from torch.nn.attention import SDPBackend, sdpa_kernel + + from invokeai.backend.flux2 import denoise_working_memory + from invokeai.backend.util.devices import TorchDevice + from invokeai.backend.working_memory.measurement import measure_cold_and_warm + from invokeai.backend.working_memory.platform import platform_for + + # The device the node itself takes. + device = TorchDevice.choose_torch_device() + if device.type != "cuda": + raise SystemExit(f"Needs a CUDA or ROCm device; this process would run on {device}.") + + def kernel(): + return sdpa_kernel(SDPBackend.MATH) if point.force_math else nullcontext() + + with torch.inference_mode(): + # In the forward's kernel regime, so the score matrix is priced for the kernel that runs. + with kernel(): + estimate = _estimate(point, device) + platform = platform_for(device) + facts = fresh_points.runtime_facts(device, platform.os, denoise_working_memory.__file__) + common = { + "estimator": estimate.estimator, + "version": estimate.version, + "platform": dataclasses.asdict(platform), + "features": dict(estimate.features), + "device": facts["device"], + } + + transformer = _transformer(point.hidden_size, point.blocks) + weights = _weight_bytes(transformer) + free, _ = TorchDevice.cuda_mem_get_info(device) + raw = { + "reservation_bytes": estimate.reservation_bytes, + "weight_bytes": weights, + "predicted_bytes": estimate.reservation_bytes + weights, + "free_bytes": free, + "runtime": facts, + } + if raw["predicted_bytes"] > free and not force: + return fresh_points.skipped_line(point, common, raw) + + # Allocated directly in bf16 on the device and randomly initialized: [dev]'s blocks would otherwise pass + # through the host in float32 first. + transformer = transformer.to(getattr(torch, DTYPE)).to_empty(device=device) + for param in transformer.parameters(): + torch.nn.init.normal_(param, std=0.02) + transformer.eval() + inputs = _inputs(point, device) + + def forward() -> object: + with kernel(): + return transformer(**inputs) + + windows = measure_cold_and_warm(device, forward) + date = datetime.date.today().isoformat() + return fresh_points.point_line( + point, common=common, provenance=provenance(point, facts, date), windows=windows, raw=raw + ) + + +_COLUMNS = ( + f"{'kind':>8} {'width':>5} {'blocks':>6} {'px':>5} {'text':>5} {'refs':>4} {'ref px':>6} {'batch':>5} {'tokens':>7}" +) + + +def _grid(point: Mapping[str, Any]) -> str: + return ( + f"{point['kind']:>8} {point['hidden_size']:>5} {point['blocks']:>6} {point['px']:>5} {point['text_tokens']:>5} " + f"{point['references']:>4} {point['reference_px'] if point['references'] else '':>6} {point['batch']:>5}" + ) + + +def _attended(line: Mapping[str, Any]) -> int: + """The tokens of every sample of a measured line: its size on the line.""" + return line["features"]["tokens"] * line["features"]["batch_size"] + + +def _head(line: Mapping[str, Any]) -> str: + """A line's grid columns in the table.""" + return f"{_grid(line['point'])} {_attended(line) if 'features' in line else '':>7}" + + +def _px(line: Mapping[str, Any]) -> int: + return line["point"]["px"] + + +def _summarize_fit(lines: Sequence[Mapping[str, Any]], text_tokens: int) -> None: + measured = [line for line in lines if line["status"] == "point"] + grid = [line for line in measured if line["fit"]] + # The line's slopes from the points that fit it; the extra-blocks forward compares with any grid point. + fit = [line for line in grid if line["in_fit"]] + for width in dict.fromkeys(line["point"]["hidden_size"] for line in fit): + of_width = [line for line in fit if line["point"]["hidden_size"] == width] + plain = [line for line in of_width if line["point"]["references"] == 0 and line["point"]["batch"] == 1] + main = sorted((line for line in plain if line["point"]["text_tokens"] == text_tokens), key=_attended) + for line in main: + print(f"width {width}, {_px(line)}px: {line['measured_bytes'] / _attended(line) / MIB:.4f} MiB per token") + if len(main) < 2: + continue + lo, hi = main[0], main[-1] + slope = (hi["measured_bytes"] - lo["measured_bytes"]) / (_attended(hi) - _attended(lo)) + base = lo["measured_bytes"] - slope * _attended(lo) + print(f"width {width}, {_px(lo)}-{_px(hi)}px: {slope / MIB:.4f} MiB per token, intercept {base / GIB:.3f} GiB") + # Reference images and batches sit on the same line, which `build_shipped.py` fits on all of them. + for line in of_width: + if line in main: + continue + on_line = base + slope * _attended(line) + point = line["point"] + print( + f"width {width}, {_px(line)}px, {point['text_tokens']} text, {point['references']} references, batch " + f"{point['batch']}: {line['measured_bytes'] / GIB:.3f} GiB at {_attended(line)} tokens, " + f"{line['measured_bytes'] / on_line:.3f}x the {_px(lo)}-{_px(hi)}px line" + ) + for line in (line for line in measured if line["point"]["kind"] == "blocks"): + same = ("hidden_size", "px", "text_tokens", "references", "batch") + base_line = next((ref for ref in grid if all(ref["point"][key] == line["point"][key] for key in same)), None) + if base_line is not None: + extra = line["measured_bytes"] - base_line["measured_bytes"] + print( + f"{line['point']['blocks']} against {base_line['point']['blocks']} blocks per stream, width " + f"{line['point']['hidden_size']}, {_px(line)}px: {extra / MIB:+.0f} MiB " + f"({line['measured_bytes'] / base_line['measured_bytes']:.3f}x)" + ) + + +def _summarize_large(lines: Sequence[Mapping[str, Any]]) -> None: + for line in (line for line in lines if line["status"] == "point"): + point, reserved = line["point"], line["raw"]["reservation_bytes"] + print( + f"width {point['hidden_size']}, {_px(line)}px + {point['references']}x{point['reference_px']}px: " + f"{line['measured_bytes'] / GIB:.3f} GiB at {_attended(line)} tokens, " + f"{line['measured_bytes'] / _attended(line) / MIB:.4f} MiB per token; the shipped estimate reserves " + f"{reserved / GIB:.3f} GiB ({reserved / line['measured_bytes']:.3f}x)" + ) + + +def _summarize_checks(lines: Sequence[Mapping[str, Any]]) -> None: + from invokeai.backend.util.attention import SDPA_MATH_BYTES_PER_SCORE_ELEMENT + + measured = [line for line in lines if line["status"] == "point"] + for line in (line for line in measured if line["point"]["kind"] == "math"): + point = line["point"] + baseline = next( + ( + ref + for ref in measured + if ref["point"]["kind"] == "baseline" and ref["point"]["hidden_size"] == point["hidden_size"] + ), + None, + ) + if baseline is None: + print(f"math kernel, width {point['hidden_size']}: no default-kernel forward to compare with") + continue + extra = line["measured_bytes"] - baseline["measured_bytes"] + charged = line["raw"]["reservation_bytes"] - baseline["raw"]["reservation_bytes"] + tokens = line["features"]["tokens"] + elements = point["hidden_size"] // HEAD_DIM * line["features"]["batch_size"] * tokens * tokens + print( + f"math kernel, width {point['hidden_size']}, {point['px']}px: {extra / GIB:.3f} GiB over the default " + f"kernel, {extra / elements:.2f} bytes per score element; the estimate adds {charged / GIB:.3f} GiB " + f"({SDPA_MATH_BYTES_PER_SCORE_ELEMENT} bytes per element, one chunk where the ROCm guard chunks)" + ) + + +def _print_plan(points: Sequence[Point]) -> None: + """The planned points, each priced as a CPU prices it, plus the weights: the estimate with cuDNN's native-allocator + values floored at the legacy constants, as every unmeasured platform's, the forced math kernel's score matrix + included.""" + import torch + from torch.nn.attention import SDPBackend, sdpa_kernel + + print(f"{_COLUMNS} {'estimate GiB':>12} {'weights GiB':>11} {'total GiB':>9}") + cpu = torch.device("cpu") + weights_of: dict[tuple[int, int], int] = {} + for point in points: + with sdpa_kernel(SDPBackend.MATH) if point.force_math else nullcontext(): + estimate = _estimate(point, cpu) + geometry = (point.hidden_size, point.blocks) + if geometry not in weights_of: + weights_of[geometry] = _weight_bytes(_transformer(*geometry)) + weights = weights_of[geometry] + tokens = estimate.features["tokens"] * point.batch + print( + f"{_grid(dataclasses.asdict(point))} {tokens:>7} {estimate.reservation_bytes / GIB:>12.2f} " + f"{weights / GIB:>11.2f} {(estimate.reservation_bytes + weights) / GIB:>9.2f}" + f"{' forced math' if point.force_math else ''}" + ) + print(f"{len(points)} forwards, one process each") + + +def _ints(text: str) -> list[int]: + try: + return [int(part) for part in text.split(",") if part.strip()] + except ValueError: + raise argparse.ArgumentTypeError(f"not a comma-separated list of integers: {text!r}") + + +def _sizes(text: str) -> list[int]: + sizes = _ints(text) + multiple = LATENT_SCALE_FACTOR * PATCH_SIZE + if any(px <= 0 or px % multiple for px in sizes): + raise argparse.ArgumentTypeError(f"sizes must be positive multiples of {multiple} px, got {text!r}") + return sizes + + +def _widths(text: str) -> list[int]: + widths = _ints(text) + if not widths or not set(widths) <= set(CONTEXT_DIMS): + raise argparse.ArgumentTypeError(f"widths must be among {', '.join(map(str, CONTEXT_DIMS))}, got {text!r}") + return widths + + +def _side(text: str) -> int: + try: + side = int(text) + except ValueError: + raise argparse.ArgumentTypeError(f"not a side length in px: {text!r}") + if side < 2 * LATENT_SCALE_FACTOR: + raise argparse.ArgumentTypeError(f"a reference must be at least {2 * LATENT_SCALE_FACTOR} px, got {side}") + return side + + +def _counts(text: str) -> list[int]: + counts = _ints(text) + if any(count < 0 for count in counts): + raise argparse.ArgumentTypeError(f"counts must not be negative, got {text!r}") + return counts + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--mode", choices=("fit", "large", "math"), default="fit", help="Default: fit.") + parser.add_argument("--out", type=Path, help="The JSON-lines file the lines are appended to. Required to measure.") + parser.add_argument( + "--widths", type=_widths, help="Transformer widths. Default: all, and 3072 and 4096 in large mode." + ) + parser.add_argument("--sizes", type=_sizes, default="512,768,1024,1440,2048", help="The fit grid's sizes, px.") + parser.add_argument("--text-tokens", type=int, default=512, help="The caption at every size. Default 512.") + parser.add_argument("--short-text-tokens", type=int, default=77, help="The text-slope caption. Default 77.") + parser.add_argument( + "--short-text-sizes", type=_sizes, default="1024,1440", help="Fit sizes that also run the text-slope caption." + ) + parser.add_argument( + "--references", type=_counts, default="1,3", help="Reference images at 1024px, each run. Default 1,3." + ) + parser.add_argument( + "--reference-px", type=_side, default=REFERENCE_PX, help="The fit grid's reference side, px. Default 1024." + ) + parser.add_argument("--batches", type=_counts, default="2", help="Batch sizes at 1024px beyond 1. Default 2.") + parser.add_argument("--blocks", type=int, default=2, help="Blocks per stream. Default 2.") + parser.add_argument("--extra-blocks", type=int, default=0, help="Fit mode: this block count at 1024px as well.") + parser.add_argument( + "--alloc-conf", + default="", + help="The allocator configuration, as the server's pytorch_cuda_alloc_conf. Empty: the platform's default.", + ) + parser.add_argument("--force", action="store_true", help="Run points predicted not to fit free device memory.") + parser.add_argument("--dry-run", action="store_true", help="Print the planned points without touching a device.") + parser.add_argument("--single", help=argparse.SUPPRESS) + args = parser.parse_args() + + if args.single is not None: + fresh_points.measure_in_this_process(args.single, point_type=Point, measure=_measure, logger_name=LOGGER_NAME) + widths = args.widths or list(LARGE_WIDTHS if args.mode == "large" else CONTEXT_DIMS) + points = plan( + args.mode, + widths=widths, + sizes=args.sizes, + text_tokens=args.text_tokens, + short_text_tokens=args.short_text_tokens, + short_text_sizes=args.short_text_sizes, + references=args.references, + batches=args.batches, + blocks=args.blocks, + extra_blocks=args.extra_blocks, + reference_px=args.reference_px, + ) + if args.dry_run: + _print_plan(points) + return + if args.out is None: + parser.error("--out is required to measure") + fresh_points.check_alloc_conf(parser, args.alloc_conf) + lines = fresh_points.measure_points( + points, + script=Path(__file__).resolve(), + out=args.out, + alloc_conf=args.alloc_conf, + force=args.force, + logger_name=LOGGER_NAME, + columns=_COLUMNS, + head=_head, + ) + + print() + if args.mode == "fit": + _summarize_fit(lines, args.text_tokens) + elif args.mode == "large": + _summarize_large(lines) + else: + _summarize_checks(lines) + fresh_points.print_counts(lines, args.out) + + +if __name__ == "__main__": + main() diff --git a/scripts/calibrate_flux2_working_memory.py b/scripts/calibrate_flux2_working_memory.py index 39d52fb7a2f..32368e5c134 100644 --- a/scripts/calibrate_flux2_working_memory.py +++ b/scripts/calibrate_flux2_working_memory.py @@ -1,53 +1,28 @@ -"""Calibrate the FLUX.2 working-memory estimates against measured peak CUDA/HIP memory. - -Background ----------- -Four constants decide how much VRAM the model cache keeps free for a FLUX.2 operation, and every one -of them was fitted on CUDA: - -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 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 - build the score matrix? - -The estimate is consumed by the model cache via ``free >= estimate`` to decide what to evict, so it -MUST be an upper bound: this measures peak *reserved* (not merely allocated) memory, the conservative -quantity that includes caching-allocator overhead and kernel scratch. - -Why this script exists ----------------------- -ROCm answers (1), (2) and (4) differently from CUDA -- its fused kernels cap the head dim at 128, so -the FLUX.2 VAE's 512-wide mid-block head falls back to ``math`` and the score matrix becomes a real, -dominant term. The constants shipped today are known to be short on ROCm for VAE decode in the middle -of the resolution range. Recalibrating needs the hardware, so this puts the whole measurement in one -runnable file: run it on an AMD card and paste the output. - -Portability ------------ -Backend-agnostic: only ``torch.cuda.*``, which is the same API on NVIDIA/CUDA and AMD/ROCm (HIP) -builds. Run the SAME script on each backend and compare. The curve *shape* is architectural and -should match; the absolute constants can differ (cuDNN vs MIOpen workspaces, fused-kernel -availability, allocator rounding). Ship the max across backends, plus headroom. - -No checkpoints required. Every measurement uses a randomly initialized model at the real geometry -- -memory depends on shapes, not on weight values, and a stock-config ``AutoencoderKLFlux2`` reproduces -the real-weight 1024px decode measurement to within 2%. Pass ``--vae`` to measure a specific VAE (the -small-decoder variant has different ``block_out_channels``). - -Each point is measured in a FRESH SUBPROCESS so the caching allocator's fragmentation history cannot -contaminate the reserved-delta reading. A point that OOMs is recorded as ``oom`` rather than aborting -the run, so the grid can probe up to the card's ceiling safely. +"""Measure what a materialized SDPA score matrix costs, and whether this build materializes one. + +What it measures +---------------- +`sdpa_score_matrix_bytes` (``backend/util/attention.py``) charges a working-memory estimate for the score matrix only +where SDPA has no fused kernel for the shapes and builds it, at ``SDPA_MATH_BYTES_PER_SCORE_ELEMENT`` bytes per +element. Two sections check both halves on the build it runs on: + +1. ``dispatch``: which kernel this build's SDPA picks for a 128-wide head (the transformers') and a 512-wide one (the + FLUX.2 VAE mid-block), with and without an additive mask -- the answer the estimators' probe gets. +2. ``sdpa``: peak reserved bytes per score element with the math kernel forced, over the shapes the estimators + produce. The constant must be at least the largest; the measured table is in ``backend/util/attention.py``. + +The estimators' coefficients are measured elsewhere: the denoise lines by fresh-process calibration scripts such as +``scripts/calibrate_flux2_denoise_working_memory.py``, the VAE lines by calibration runs in a running server whose +exports ``scripts/working_memory/build_shipped.py`` turns into shipped points. + +Each point is measured in a fresh subprocess, so the caching allocator's history cannot contaminate the reserved-delta +reading; a point that runs out of memory is recorded as ``oom`` rather than aborting the run. Only ``torch.cuda.*`` is +used, the same API on CUDA and ROCm (HIP) builds: run it on each build and compare. Usage ----- python scripts/calibrate_flux2_working_memory.py - python scripts/calibrate_flux2_working_memory.py --only vae --csv flux2_rocm.csv - python scripts/calibrate_flux2_working_memory.py --only denoise --max-px 1024 + python scripts/calibrate_flux2_working_memory.py --only sdpa --csv sdpa_rocm.csv """ import argparse @@ -60,53 +35,10 @@ import torch.nn.functional as F from torch.nn.attention import SDPBackend, sdpa_kernel -from invokeai.app.invocations.flux2.flux2_denoise import ( - FLUX2_ATTENTION_HEAD_DIM, - FLUX2_REFERENCE_HIDDEN_SIZE, - Flux2DenoiseInvocation, -) -from invokeai.backend.model_manager.configs.flux2_variant import flux2_hidden_size -from invokeai.backend.model_manager.taxonomy import Flux2VariantType -from invokeai.backend.util.attention import SDPA_MATH_BYTES_PER_SCORE_ELEMENT, sdpa_score_matrix_bytes -from invokeai.backend.util.vae_working_memory import VAE_FLUX2_SPEC, estimate_vae_working_memory_flux2 -from invokeai.backend.working_memory.coefficients import shipped_coefficients -from invokeai.backend.working_memory.platform import platform_for +from invokeai.backend.util.attention import SDPA_MATH_BYTES_PER_SCORE_ELEMENT GIB = 1024**3 -MIB = 1024**2 - - -_VAE_LINE_DTYPES = {"float16": "fp16", "bfloat16": "bf16", "float32": "fp32"} - - -def _vae_line_dtypes() -> list[str]: - """The `--dtype` values the FLUX.2 VAE ships a line for: the loaders run it in bf16 or fp32, never fp16.""" - return [name for name, dtype in _VAE_LINE_DTYPES.items() if f"k_decode_{dtype}" in VAE_FLUX2_SPEC.coefficients] - - -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 = _VAE_LINE_DTYPES[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 -# `_FLUX2_VAE_*` in vae_working_memory.py. -VAE_SPATIAL_COMPRESSION = 8 -VAE_MID_BLOCK_HEAD_DIM = 512 -LATENT_SCALE_FACTOR = 8 - -# (variant, transformer hidden size, context_in_dim). The source of truth is -# `model_manager/configs/flux2_variant.py`; `_check_variant_table` asserts we have not drifted. -VARIANTS = [ - (Flux2VariantType.Klein4B, 3072, 7680), - (Flux2VariantType.Klein9B, 4096, 12288), - (Flux2VariantType.Dev, 6144, 15360), -] - -DEFAULT_VAE_PX = [512, 768, 1024, 1280, 1536] # (heads, seq, head_dim) for the score-matrix constant. Spans the shapes the two estimators actually # produce: the VAE's single 512-wide head, and the transformer's many 128-wide ones. @@ -118,12 +50,6 @@ def _shipped_vae_line(operation: str, dtype_name: str) -> tuple[float, float]: (48, 4608, 128), ] -# Sequence lengths the denoise slope is taken between. 4608 is a plain 1024x1024 generation -# (4096 image + 512 text); 9216 is that doubled, which is also what a batch of 2 costs. -DENOISE_SEQ_SHORT = 4608 -DENOISE_SEQ_LONG = 9216 -DENOISE_TEXT_TOKENS = 512 - DTYPES = {"float16": torch.float16, "bfloat16": torch.bfloat16, "float32": torch.float32} # Environment that materially changes a peak-*reserved* reading or which kernel runs. Reported in the @@ -147,14 +73,6 @@ def _shipped_vae_line(operation: str, dtype_name: str) -> tuple[float, float]: } -def _check_variant_table() -> None: - """Fail loudly if the widths hard-coded above ever drift from the model manager's table.""" - for variant, hidden, _ in VARIANTS: - actual = flux2_hidden_size(variant) - if actual != hidden: - raise SystemExit(f"Variant table is stale: {variant.value} is {actual}, not {hidden}.") - - def _peak_reserved(fn) -> int | None: """Run ``fn`` and return the growth in peak reserved bytes, or ``None`` if it OOMed. @@ -227,160 +145,11 @@ def run() -> None: return row -# -------------------------------------------------------------------------------------------- -# 3. The VAE's linear constants -# -------------------------------------------------------------------------------------------- - - -def _load_vae(vae_path: str | None, dtype: torch.dtype): - from diffusers import AutoencoderKLFlux2 - - if vae_path: - return AutoencoderKLFlux2.from_pretrained(vae_path, local_files_only=True, torch_dtype=dtype) - # Weight values do not affect activation memory, only shapes do; the stock config is the real - # FLUX.2 VAE geometry (32 latent channels, block_out_channels ending at 512). - return AutoencoderKLFlux2().to(dtype=dtype) - - -@torch.inference_mode() -def measure_vae(operation: str, px: int, dtype: torch.dtype, force_math: bool, vae_path: str | None) -> dict: - """Peak reserved memory for one untiled decode/encode, against what the estimator predicts. - - ``force_math`` runs the mid-block attention through SDPA's ``math`` fallback even where a fused - kernel exists, so a CUDA box can approximate the regime ROCm is in permanently. It is not a - substitute for measuring on ROCm -- the two are not equivalent, which is itself worth showing. - """ - device = torch.device("cuda") - vae = _load_vae(vae_path, dtype).to(device).eval() - vae.disable_tiling() # the decode/encode invocations do not tile; match them. - - param = next(vae.parameters()) - element_size = param.element_size() - if operation == "decode": - latent_channels = int(vae.config.latent_channels) - x = torch.randn(1, latent_channels, px // LATENT_SCALE_FACTOR, px // LATENT_SCALE_FACTOR, **_td(device, dtype)) - else: - x = torch.randn(1, 3, px, px, **_td(device, dtype)) - - def run() -> None: - ctx = sdpa_kernel([SDPBackend.MATH]) if force_math else _null_context() - with ctx: - if operation == "decode": - vae.decode(x, return_dict=False) - else: - vae.encode(x, return_dict=False)[0].mode() - - peak = _peak_reserved(run) - row = { - "operation": operation, - "px": px, - "force_math": force_math, - "element_size": element_size, - "oom": peak is None, - } - if peak is None: - return row - - estimate = estimate_vae_working_memory_flux2( - operation=operation, image_tensor=x, vae=vae, device=device - ).reservation_bytes - seq_len = (px // VAE_SPATIAL_COMPRESSION) ** 2 - score_bytes = sdpa_score_matrix_bytes( - device=device, dtype=param.dtype, num_heads=1, head_dim=VAE_MID_BLOCK_HEAD_DIM, seq_len=seq_len - ) - if force_math and score_bytes == 0: - # Fused here, so the estimator charged nothing -- but we forced math, so price it anyway. - score_bytes = seq_len * seq_len * SDPA_MATH_BYTES_PER_SCORE_ELEMENT - estimate = max(estimate, score_bytes) - - return row | { - "reserved_delta": peak, - "estimate": estimate, - "score_term": score_bytes, - # 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), - "covered": peak <= estimate, - } - - -# -------------------------------------------------------------------------------------------- -# 4. The denoise per-token constant, and its width scaling -# -------------------------------------------------------------------------------------------- - - -@torch.inference_mode() -def measure_denoise(hidden: int, context_dim: int, seq_len: int, blocks: int, dtype: torch.dtype) -> dict: - """Peak reserved memory for one transformer forward at a given width and sequence length. - - Uses a reduced block count on purpose: the per-token cost is block-count independent, because a - no-grad forward frees each block's intermediates as it goes. ``--blocks`` measures a second point - so that assumption can be re-checked on this build rather than inherited. - """ - from diffusers import Flux2Transformer2DModel - - device = torch.device("cuda") - model = ( - Flux2Transformer2DModel( - num_layers=blocks, - num_single_layers=blocks, - num_attention_heads=hidden // FLUX2_ATTENTION_HEAD_DIM, - attention_head_dim=FLUX2_ATTENTION_HEAD_DIM, - joint_attention_dim=context_dim, - ) - .to(device=device, dtype=dtype) - .eval() - ) - - img_tokens = seq_len - DENOISE_TEXT_TOKENS - kwargs = { - "hidden_states": torch.randn(1, img_tokens, 128, **_td(device, dtype)), - "encoder_hidden_states": torch.randn(1, DENOISE_TEXT_TOKENS, context_dim, **_td(device, dtype)), - "timestep": torch.full((1,), 0.5, **_td(device, dtype)), - "img_ids": torch.zeros(img_tokens, 4, **_td(device, dtype)), - "txt_ids": torch.zeros(DENOISE_TEXT_TOKENS, 4, **_td(device, dtype)), - "guidance": torch.full((1,), 4.0, **_td(device, dtype)), - "return_dict": False, - } - - # Warm up first, then drop the allocator's cache: the cold call pays one-off weight-cast and - # workspace costs that the per-token slope must not absorb. The slope is a difference between - # two of these, so any constant overhead cancels either way -- warming just reduces the noise. - try: - model(**kwargs) - except (torch.cuda.OutOfMemoryError, RuntimeError) as e: - if "out of memory" not in str(e).lower(): - raise - return {"hidden": hidden, "seq_len": seq_len, "blocks": blocks, "oom": True} - - peak = _peak_reserved(lambda: model(**kwargs)) - row = {"hidden": hidden, "seq_len": seq_len, "blocks": blocks, "oom": peak is None} - if peak is not None: - row |= {"reserved_delta": peak} - return row - - # -------------------------------------------------------------------------------------------- # plumbing # -------------------------------------------------------------------------------------------- -def _td(device: torch.device, dtype: torch.dtype) -> dict: - return {"device": device, "dtype": dtype} - - -class _null_context: - def __enter__(self): - return self - - def __exit__(self, *args): - return False - - def _report_environment() -> None: """Print the env vars that can move these numbers, and warn about the one that invalidates them. @@ -405,26 +174,6 @@ def _report_environment() -> None: ) -def _flag_non_monotonic(rows: list[dict]) -> None: - """Peak cannot fall as the input grows. If it does, the reading is not measuring what it claims.""" - for operation in ("decode", "encode"): - for force_math in (False, True): - series = [ - r for r in rows if r["operation"] == operation and r["force_math"] is force_math and not r.get("oom") - ] - series.sort(key=lambda r: r["px"]) - for earlier, later in zip(series, series[1:], strict=False): - if later["reserved_delta"] < earlier["reserved_delta"]: - print( - f" WARNING: {operation} peak FELL from {earlier['px']}px to {later['px']}px " - f"({earlier['reserved_delta'] / GIB:.3f} -> {later['reserved_delta'] / GIB:.3f} GiB, " - f"force_math={force_math}). Peak cannot decrease as the input grows; the larger " - "points are being clipped (allocator GC threshold, or another process freeing " - "memory). Do not fit a constant to this run." - ) - break - - def _run_point(args: list[str]) -> dict | None: """Run one measurement in a fresh subprocess and return its JSON row.""" proc = subprocess.run([sys.executable, __file__, "--single", *args], capture_output=True, text=True) @@ -475,212 +224,44 @@ def report_sdpa(shapes: list[tuple[int, int, int]], dtype_name: str) -> list[dic return rows -def report_vae(pxs: list[int], dtype_name: str, vae_path: str | None) -> list[dict]: - (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") - print("has none -- on cuDNN the forced-math decode measures *below* the fused one until 1536px,") - print("because the memory-efficient kernel's workspace is the larger term. Only a real run on the") - print("materializing build calibrates it.\n") - print( - f"{'op':7} {'px':>5} {'math':>5} {'measured(GiB)':>14} {'estimate(GiB)':>14} {'implied_k':>10} {'covered':>8}" - ) - print("-" * 70) - rows = [] - for operation in ("decode", "encode"): - for px in pxs: - for force_math in (False, True): - args = ["vae", operation, str(px), dtype_name, "1" if force_math else "0"] - if vae_path: - args.append(vae_path) - row = _run_point(args) - if row is None: - continue - rows.append(row) - if row.get("oom"): - print(f"{operation:7} {px:>5} {str(force_math):>5} {'OOM':>14}") - continue - k = row["implied_linear_constant"] - print( - f"{operation:7} {px:>5} {str(force_math):>5} {row['reserved_delta'] / GIB:>14.3f} " - f"{row['estimate'] / GIB:>14.3f} {(f'{k:.0f}' if k else 'n/a'):>10} " - f"{('yes' if row['covered'] else 'NO'):>8}" - ) - print("") - _flag_non_monotonic(rows) - for operation in ("decode", "encode"): - # Compare against the column this build actually selects, not against a hard-coded number. - shipped_k, shipped_b = _shipped_vae_line(operation, dtype_name) - for force_math in (False, True): - ks = [ - 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 - and not r.get("oom") - # Skip rows where the score matrix, not the convolution phase, is what the estimate - # is bounding: there the linear constant is not the thing under test. - and r["score_term"] < r["reserved_delta"] - ] - if not ks: - continue - mode = "math" if force_math else "fused" - 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:") - for r in short: - gap = (r["reserved_delta"] - r["estimate"]) / GIB - # The cache never reserves less than `device_working_mem_gb`, so a point under that floor - # is not actually short in production. - floored = " (absorbed by the 3GB device_working_mem_gb floor)" if r["reserved_delta"] < 3 * GIB else "" - print(f" {r['operation']} {r['px']}px force_math={r['force_math']}: short by {gap:.2f} GiB{floored}") - return rows - - -def report_denoise(dtype_name: str, blocks: int, extra_blocks: int | None) -> list[dict]: - print("\n=== 4. Denoise per-token constant and its width scaling ===") - print(f"Shipped: {0.4:.1f} MB/token at hidden={FLUX2_REFERENCE_HIDDEN_SIZE}, scaled linearly by width.\n") - print( - f"{'variant':10} {'hidden':>7} {'blocks':>7} {'short(GiB)':>11} {'long(GiB)':>10} {'MB/token':>9} {'ratio':>7}" - ) - print("-" * 68) - rows = [] - slopes: dict[int, float] = {} - block_counts = [blocks] + ([extra_blocks] if extra_blocks else []) - for variant, hidden, context_dim in VARIANTS: - for nb in block_counts: - pair = [] - for seq in (DENOISE_SEQ_SHORT, DENOISE_SEQ_LONG): - row = _run_point(["denoise", str(hidden), str(context_dim), str(seq), str(nb), dtype_name]) - if row is None or row.get("oom"): - pair = [] - break - rows.append(row) - pair.append(row["reserved_delta"]) - if not pair: - print(f"{variant.value:10} {hidden:>7} {nb:>7} {'OOM':>11}") - continue - slope = (pair[1] - pair[0]) / (DENOISE_SEQ_LONG - DENOISE_SEQ_SHORT) - if nb == blocks: - slopes[hidden] = slope - print( - f"{variant.value:10} {hidden:>7} {nb:>7} {pair[0] / GIB:>11.3f} {pair[1] / GIB:>10.3f} " - f"{slope / MIB:>9.4f} {slope / slopes.get(FLUX2_REFERENCE_HIDDEN_SIZE, slope):>7.3f}" - ) - - if extra_blocks: - print(f"\n The two block counts should agree per width; if they do not, the '{blocks} blocks stand in") - print(" for the real model' assumption does not hold on this build and the rest is suspect.") - - reference = slopes.get(FLUX2_REFERENCE_HIDDEN_SIZE) - if reference: - shipped = 0.4 * MIB - verdict = "OK" if shipped >= reference else "SHORT" - print( - f"\n hidden={FLUX2_REFERENCE_HIDDEN_SIZE}: measured {reference / MIB:.4f} MB/token, shipped 0.4 -> {verdict}" - ) - for hidden, slope in sorted(slopes.items()): - print( - f" hidden={hidden}: slope ratio {slope / reference:.3f} against width ratio " - f"{hidden / FLUX2_REFERENCE_HIDDEN_SIZE:.3f}" - ) - print("\n Those two columns should match: that is the claim that width scales the constant.") - # What the full estimator would reserve for the case #9500 describes. - for variant, hidden, _ in VARIANTS: - est = Flux2DenoiseInvocation._estimate_working_memory( - None, image_seq_len=4096, ref_image_seq_len=12288, text_seq_len=512, num_loras=0, hidden_size=hidden - ) - need = slopes.get(hidden, reference) * 16896 - print( - f" {variant.value:10} 1024px + 3 refs: estimate {est / GIB:5.2f} GiB, " - f"measured slope implies {need / GIB:5.2f} GiB" - ) - return rows - - def main() -> None: parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( "--only", - choices=["dispatch", "sdpa", "vae", "denoise"], + choices=["dispatch", "sdpa"], action="append", - help="Run only these sections (repeatable). Default: all four.", + help="Run only these sections (repeatable). Default: both.", ) parser.add_argument("--dtype", choices=list(DTYPES), default="bfloat16", help="Compute dtype. Default bfloat16.") - parser.add_argument("--max-px", type=int, default=None, help="Skip VAE resolutions above this.") - parser.add_argument("--vae", type=str, default=None, help="Optional AutoencoderKLFlux2 diffusers dir.") - parser.add_argument("--blocks", type=int, default=2, help="Transformer blocks per stream. Default 2.") - parser.add_argument( - "--extra-blocks", - type=int, - default=None, - help="Measure a second block count too, to re-check block-count independence on this build.", - ) parser.add_argument("--csv", type=str, default=None, help="Write the raw rows to CSV.") # Internal: measure one point in this process and print one JSON line. parser.add_argument("--single", nargs="*", default=None, help=argparse.SUPPRESS) args = parser.parse_args() - sections = args.only or ["dispatch", "sdpa", "vae", "denoise"] - if "vae" in sections and args.dtype not in _vae_line_dtypes(): - # Refused up front: the VAE section compares against the shipped line, and runs after dispatch and sdpa. - parser.error( - f"--dtype {args.dtype} has no FLUX.2 VAE line to measure against; the VAE runs in " - f"{', '.join(_vae_line_dtypes())}. Pick one of those, or leave the VAE out with --only." - ) if not torch.cuda.is_available(): raise SystemExit("No CUDA/HIP device available.") - _check_variant_table() if args.single: kind, rest = args.single[0], args.single[1:] if kind == "sdpa": heads, seq, head_dim, dtype_name = int(rest[0]), int(rest[1]), int(rest[2]), rest[3] print(json.dumps(measure_sdpa(heads, seq, head_dim, DTYPES[dtype_name]))) - elif kind == "vae": - operation, px, dtype_name, force_math = rest[0], int(rest[1]), rest[2], rest[3] == "1" - vae_path = rest[4] if len(rest) > 4 else None - print(json.dumps(measure_vae(operation, px, DTYPES[dtype_name], force_math, vae_path))) - elif kind == "denoise": - hidden, context_dim, seq, blocks, dtype_name = ( - int(rest[0]), - int(rest[1]), - int(rest[2]), - int(rest[3]), - rest[4], - ) - print(json.dumps(measure_denoise(hidden, context_dim, seq, blocks, DTYPES[dtype_name]))) else: raise SystemExit(f"unknown point kind {kind}") return + sections = args.only or ["dispatch", "sdpa"] print( f"torch {torch.__version__} | device {torch.cuda.get_device_name(0)} | " f"hip={torch.version.hip} | dtype={args.dtype}" ) _report_environment() - pxs = [p for p in DEFAULT_VAE_PX if args.max_px is None or p <= args.max_px] rows: list[dict] = [] if "dispatch" in sections: rows += [{"section": "dispatch", **r} for r in report_dispatch(args.dtype)] if "sdpa" in sections: rows += [{"section": "sdpa", **r} for r in report_sdpa(DEFAULT_SDPA_SHAPES, args.dtype)] - if "vae" in sections: - rows += [{"section": "vae", **r} for r in report_vae(pxs, args.dtype, args.vae)] - if "denoise" in sections: - rows += [{"section": "denoise", **r} for r in report_denoise(args.dtype, args.blocks, args.extra_blocks)] if args.csv: import csv diff --git a/scripts/calibrate_z_image_working_memory.py b/scripts/calibrate_z_image_working_memory.py index 7bd5215289b..5a7859dfa52 100644 --- a/scripts/calibrate_z_image_working_memory.py +++ b/scripts/calibrate_z_image_working_memory.py @@ -10,17 +10,18 @@ peak), or after which Windows keeps more than `measurement.SPILL_TOLERANCE_BYTES` of the process in shared memory, is invalid: it is recorded as such, not as a point. -Every point runs in a fresh process, which first applies the server's runtime (`apply_server_runtime` in -`invokeai/app/run_app.py`): the `MIOPEN_FIND_MODE` default, the integrated-GPU check, the allocator, the AOTriton -decision and the monkeypatches, the ROCm SDPA guard among them, in the server's order. Its settings are those of a server -without a config file, except the allocator configuration: `--alloc-conf`, or without one the platform default the -server uses (expandable segments for ROCm on Windows, torch's native allocator elsewhere). This script's own process runs -the integrated-GPU check once before the first point: the measuring processes inherit the `HIP_VISIBLE_DEVICES` it sets, -which their own check keeps, as the server keeps an exported one. - -The forward then runs twice. The first forward after a load is a real peak in a server (kernel modules, library -workspaces), and so is a later one, so the point takes the larger demand. Where both forwards read the driver's figure -the point is a `demand_peak`. Where one could not, the point falls back to torch's counters alone, as a `reserved_peak`. +Every point runs in a fresh process (`scripts/working_memory/fresh_points.py`, which the calibration scripts share), +which first applies the server's runtime (`apply_server_runtime` in `invokeai/app/run_app.py`): the `MIOPEN_FIND_MODE` +default, the integrated-GPU check, the allocator, the AOTriton decision and the monkeypatches, the ROCm SDPA guard among +them, in the server's order. Its settings are those of a server without a config file, except the allocator +configuration: `--alloc-conf`, or without one the platform default the server uses (expandable segments for ROCm on +Windows, torch's native allocator elsewhere). This script's own process runs the integrated-GPU check once before the +first point: the measuring processes inherit the `HIP_VISIBLE_DEVICES` it sets, which their own check keeps, as the +server keeps an exported one. + +The forward then runs twice, cold and warm, and the point takes the larger demand (`measure_cold_and_warm` and +`measured_bytes_and_method` in `measurement.py`, which every calibration script shares): a `demand_peak` where both +forwards read the driver's figure, else a `reserved_peak` of torch's counters alone. The transformer has the real geometry (dim 3840, 30 heads of 128, caption features 2560 wide) and random weights in the measured dtype, because memory depends on shapes, not on weight values. It has 2 main blocks and one refiner block each: @@ -91,11 +92,7 @@ import argparse import dataclasses import datetime -import json -import os -import subprocess import sys -import traceback from collections.abc import Mapping, Sequence from contextlib import nullcontext from dataclasses import dataclass @@ -103,15 +100,14 @@ from typing import TYPE_CHECKING, Any, Literal # None of these imports torch: a measuring process has to configure the allocator before torch is imported. -from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR -from invokeai.app.util.rocm_aotriton import AOTRITON_EXPERIMENTAL_ENV -from invokeai.app.util.torch_cuda_allocator import ALLOCATOR_CONF_ENV_VARS +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR # noqa: E402 +from scripts.working_memory import fresh_points # noqa: E402 if TYPE_CHECKING: import torch from invokeai.backend.fun_controlnet.z_image import ZImageFunControlVariant - from invokeai.backend.working_memory.measurement import FreshWindow GIB = 2**30 MIB = 2**20 @@ -134,13 +130,9 @@ DTYPES = ("bfloat16", "float16", "float32") LOGGER_NAME = "calibrate_z_image_working_memory" -RESULT_PREFIX = "RESULT " -POINT_TIMEOUT_S = 1800 -"""How long one measuring process may take. Generous: it only keeps a driver that hangs from stalling a whole run.""" Kind = Literal["fit", "blocks", "reference", "math", "control"] Mode = Literal["fit", "math", "control"] -PointMethod = Literal["demand_peak", "reserved_peak"] @dataclass(frozen=True) @@ -227,81 +219,8 @@ def _transformer_blocks(point: Point) -> tuple[int, int]: return max(point.blocks, len(variant.main_layers)), CONTROL_REFINER_BLOCKS -def measured_bytes_and_method(windows: Sequence["FreshWindow"]) -> tuple[int, PointMethod]: - """A valid point's bytes and `MeasuredPoint.method`, from its forwards' windows. - - `demand_peak` where every forward read the driver's figure: the largest demand. Otherwise `reserved_peak`, from - torch's counters alone, so that the method names what the bytes were read from. - """ - if all(window.driver is not None for window in windows): - return max(window.demand for window in windows if window.demand is not None), "demand_peak" - torch_counters = [delta for window in windows for delta in (window.reserved, window.allocated) if delta is not None] - return max(torch_counters), "reserved_peak" - - -def point_line( - point: Point, - *, - common: Mapping[str, Any], - provenance: str, - windows: Sequence["FreshWindow"], - raw: Mapping[str, Any], -) -> dict[str, Any]: - """The output line of a measured point: a `point`, or `invalid` with the reasons of its forwards. - - `common` holds the `MeasuredPoint` fields the measurement does not decide: estimator, version, platform, features - and device. `windows` are the forwards' fresh windows, cold first; `raw` the point's other facts. - """ - reasons = list(dict.fromkeys(reason for window in windows for reason in window.reasons)) - line = {"status": "invalid" if reasons else "point", "fit": point.fit, "point": dataclasses.asdict(point), **common} - raw = {**raw, **dict(zip(("cold", "warm"), map(dataclasses.asdict, windows), strict=False))} - if reasons: - return line | {"reasons": reasons, "raw": raw} - # Imports torch: a point line is taken in the measuring process, after `_apply_server_runtime`. - from invokeai.backend.z_image.denoise_working_memory import Z_IMAGE_DENOISE_SPEC - - measured, method = measured_bytes_and_method(windows) - on_line = Z_IMAGE_DENOISE_SPEC.line(common["features"]) - return line | { - "measured_bytes": measured, - "method": method, - "provenance": provenance, - "in_fit": point.fit and on_line is not None and on_line.fits, - "raw": raw, - } - - -def failed_line(point: Point, error: str) -> dict[str, Any]: - return {"status": "failed", "fit": point.fit, "point": dataclasses.asdict(point), "error": error} - - -def file_record(line: Mapping[str, Any]) -> dict[str, Any] | None: - """What `--out` receives of a line: a measured point, without the status only the table reads. None for a forward - that is no point: the data builder refuses a point file record that is no `MeasuredPoint`.""" - if line["status"] != "point": - return None - return {key: value for key, value in line.items() if key != "status"} - - -def _alloc_conf(facts: Mapping[str, Any]) -> str: - return ", ".join(f"{var}={value}" for var, value in facts["alloc_conf"].items()) or "no allocator configuration" - - def provenance(point: Point, facts: Mapping[str, Any], date: str) -> str: """A measured point's `provenance`: where and how it was measured.""" - build = f"torch {facts['torch']}" + (f" (HIP {facts['hip']})" if facts["hip"] else "") - parts = [ - facts["device"], - build, - facts["os"].capitalize(), - f"{_alloc_conf(facts)} (torch reports the {facts['allocator_backend']} allocator)", - ] - if facts["hip"]: - parts += [ - f"MIOPEN_FIND_MODE={facts['miopen_find_mode'] or 'unset'}", - f"{AOTRITON_EXPERIMENTAL_ENV}={facts['aotriton_experimental'] or 'unset'}", - f"ROCm SDPA guard {'installed' if facts['rocm_sdpa_guard'] else 'not installed'}", - ] main_blocks, refiner_blocks = _transformer_blocks(point) regime = { "math": ", SDPA forced onto the math kernel", @@ -311,55 +230,14 @@ def provenance(point: Point, facts: Mapping[str, Any], date: str) -> str: blocks = f"{main_blocks} main block{'' if main_blocks == 1 else 's'}" refiners = "one refiner block" if refiner_blocks == 1 else f"{refiner_blocks} refiner blocks" return ( - f"scripts/calibrate_z_image_working_memory.py ({date}): {', '.join(parts)}. Random-init Z-Image transformer " - f"with the real geometry, {blocks} and {refiners} each{regime.get(point.kind, '')}; " + f"scripts/calibrate_z_image_working_memory.py ({date}): {fresh_points.runtime_provenance(facts)}. Random-init " + f"Z-Image transformer with the real geometry, {blocks} and {refiners} each{regime.get(point.kind, '')}; " "memory does not depend on weight values. A fresh process with the server's runtime, the larger demand of a " "cold and a warm forward; no failed allocation, allocator retry or spill." ) -# The measuring process. Everything below imports torch, so it runs only after `_apply_server_runtime`. - - -def _apply_server_runtime(alloc_conf: str) -> None: - """Apply the server's runtime (`run_app.apply_server_runtime`) before torch is imported, with the settings of a - server without a config file and the allocator configuration this run measures: `alloc_conf`, or none for the - platform's default.""" - from invokeai.app.run_app import apply_server_runtime - from invokeai.app.services.config.config_default import get_config - from invokeai.backend.util.logging import InvokeAILogger - - config = get_config().model_copy(update={"pytorch_cuda_alloc_conf": alloc_conf or None}) - apply_server_runtime(config, InvokeAILogger.get_logger(LOGGER_NAME, config=config)) - - -def _runtime_facts(device: "torch.device", os_name: str) -> dict[str, Any]: - """The runtime a point is measured in, as its provenance and `raw` record it.""" - import torch - - from invokeai.backend.util.attention import rocm_sdpa_chunks_math - from invokeai.backend.working_memory.counters import backend_for, method_verified - from invokeai.backend.z_image import denoise_working_memory - - name = torch.cuda.get_device_name(device) - if torch.version.hip is not None: - name = f"{name} ({torch.cuda.get_device_properties(device).gcnArchName.split(':')[0]})" - backend = backend_for(device) - return { - "device": name, - "torch": torch.__version__, - "hip": torch.version.hip, - "os": os_name, - "allocator_backend": torch.cuda.get_allocator_backend(), - "alloc_conf": {var: os.environ[var] for var in ALLOCATOR_CONF_ENV_VARS if os.environ.get(var)}, - "miopen_find_mode": os.environ.get("MIOPEN_FIND_MODE"), - "aotriton_experimental": os.environ.get(AOTRITON_EXPERIMENTAL_ENV), - "rocm_sdpa_guard": rocm_sdpa_chunks_math(device.type), - "driver_method_verified": backend is not None and method_verified(backend), - # The checkout the point was priced from. A module's file, not the package's: `invokeai` can be imported as a - # namespace package, whose `__file__` is None. - "estimator_module": str(Path(denoise_working_memory.__file__).resolve()), - } +# The measuring process. Everything below imports torch, so it runs only after `fresh_points.apply_server_runtime`. def _transformer(blocks: int, refiner_blocks: int) -> "torch.nn.Module": @@ -413,14 +291,15 @@ def _materialize(module: "torch.nn.Module", device: "torch.device", dtype: "torc def _measure(point: Point, force: bool) -> dict[str, Any]: - """The output line of `point`, measured in this process after `_apply_server_runtime`.""" + """The output line of `point`, measured in this process after `fresh_points.apply_server_runtime`.""" import torch from torch.nn.attention import SDPBackend, sdpa_kernel from invokeai.backend.fun_controlnet.hooks import install_fun_control from invokeai.backend.util.devices import TorchDevice - from invokeai.backend.working_memory.measurement import measure + from invokeai.backend.working_memory.measurement import measure_cold_and_warm from invokeai.backend.working_memory.platform import platform_for + from invokeai.backend.z_image import denoise_working_memory from invokeai.backend.z_image.denoise_working_memory import estimate_z_image_denoise_working_memory # The device the node itself takes. @@ -450,7 +329,7 @@ def kernel(): dtype=dtype, ) platform = platform_for(device) - facts = _runtime_facts(device, platform.os) + facts = fresh_points.runtime_facts(device, platform.os, denoise_working_memory.__file__) common = { "estimator": estimate.estimator, "version": estimate.version, @@ -472,7 +351,7 @@ def kernel(): "runtime": facts, } if raw["predicted_bytes"] > free and not force: - return {"status": "skipped", "fit": point.fit, "point": dataclasses.asdict(point), **common, "raw": raw} + return fresh_points.skipped_line(point, common, raw) transformer = _materialize(transformer, device, dtype) latent = point.px // LATENT_SCALE_FACTOR @@ -493,46 +372,11 @@ def forward() -> object: with kernel(), hooks: return transformer(x=x, t=t, cap_feats=cap_feats) - windows = [measure(device, forward)] - if "oom" not in windows[0].reasons: - windows.append(measure(device, forward)) + windows = measure_cold_and_warm(device, forward) date = datetime.date.today().isoformat() - return point_line(point, common=common, provenance=provenance(point, facts, date), windows=windows, raw=raw) - - -def _measure_in_this_process(request: str) -> None: - """Measure one point in this fresh process and print its line. - - Leaves without Python's shutdown, whose HIP teardown can hang on Windows while its DLLs unload (see - `invokeai/app/util/rocm_integrated_gpu.py`).""" - spec = json.loads(request) - try: - _apply_server_runtime(spec["alloc_conf"]) - line = _measure(Point(**spec["point"]), spec["force"]) - except BaseException: - traceback.print_exc() - sys.stderr.flush() - os._exit(1) - print(RESULT_PREFIX + json.dumps(line), flush=True) - sys.stderr.flush() - os._exit(0) - - -# The driving process: plans the points, measures each in a fresh process, appends the lines, prints the table. - - -def _run(point: Point, alloc_conf: str, force: bool) -> dict[str, Any]: - request = json.dumps({"point": dataclasses.asdict(point), "alloc_conf": alloc_conf, "force": force}) - command = [sys.executable, str(Path(__file__).resolve()), "--single", request] - try: - done = subprocess.run(command, capture_output=True, text=True, errors="replace", timeout=POINT_TIMEOUT_S) - except subprocess.TimeoutExpired: - return failed_line(point, f"no result within {POINT_TIMEOUT_S} s") - for output in done.stdout.splitlines(): - if output.startswith(RESULT_PREFIX): - return json.loads(output[len(RESULT_PREFIX) :]) - tail = done.stderr.strip().splitlines()[-1:] or ["no output"] - return failed_line(point, f"exit code {done.returncode}: {tail[0]}") + return fresh_points.point_line( + point, common=common, provenance=provenance(point, facts, date), windows=windows, raw=raw + ) _COLUMNS = f"{'kind':>9} {'dtype':>8} {'blocks':>6} {'control':>8} {'px':>5} {'text':>5} {'tokens':>7}" @@ -545,37 +389,9 @@ def _grid(point: Mapping[str, Any]) -> str: ) -def _gib(value: int | None) -> str: - return "" if value is None else f"{value / GIB:.3f}" - - -def _row(line: Mapping[str, Any]) -> str: - head = f"{_grid(line['point'])} {line.get('features', {}).get('tokens', ''):>7}" - status = line["status"] - raw = line.get("raw", {}) - cold, warm = (_gib(raw[run]["demand"]) if run in raw else "" for run in ("cold", "warm")) - if status == "point": - return f"{head} {_gib(line['measured_bytes']):>10} {cold:>8} {warm:>8} {line['method']}" - if status == "invalid": - return f"{head} {'invalid':>10} {cold:>8} {warm:>8} {', '.join(line['reasons'])}" - if status == "skipped": - raw = line["raw"] - return ( - f"{head} {'skipped':>10} needs ~{raw['predicted_bytes'] / GIB:.1f} GiB, {raw['free_bytes'] / GIB:.1f} GiB " - "free (--force runs it)" - ) - return f"{head} {'FAILED':>10} {line['error']}" - - -def _runtime_header(facts: Mapping[str, Any], platform: Mapping[str, str]) -> str: - rocm = "" - if facts["hip"]: - guard = "installed" if facts["rocm_sdpa_guard"] else "NOT installed" - rocm = f" | {AOTRITON_EXPERIMENTAL_ENV}={facts['aotriton_experimental'] or 'unset'} | SDPA guard {guard}" - return ( - f"{facts['device']} | torch {facts['torch']} | {'/'.join(platform.values())} | {_alloc_conf(facts)}{rocm} | " - f"estimator from {facts['estimator_module']}" - ) +def _head(line: Mapping[str, Any]) -> str: + """A line's grid columns in the table.""" + return f"{_grid(line['point'])} {line.get('features', {}).get('tokens', ''):>7}" def _tokens(line: Mapping[str, Any]) -> int: @@ -716,8 +532,7 @@ def main() -> None: args = parser.parse_args() if args.single is not None: - _measure_in_this_process(args.single) - return + fresh_points.measure_in_this_process(args.single, point_type=Point, measure=_measure, logger_name=LOGGER_NAME) points = plan( args.mode, sizes=args.sizes, @@ -734,42 +549,24 @@ def main() -> None: return if args.out is None: parser.error("--out is required to measure") - exported = [var for var in ALLOCATOR_CONF_ENV_VARS if os.environ.get(var)] - if args.alloc_conf and exported: - # The server keeps an exported value and only warns; a measurement must not run another allocator than asked. - parser.error(f"--alloc-conf cannot apply while {', '.join(exported)} is set in the environment") - - from invokeai.app.util.rocm_integrated_gpu import hide_integrated_gpus_on_rocm_windows - from invokeai.backend.util.logging import InvokeAILogger - - # The runtime's integrated-GPU check, once for the whole run, before torch is imported anywhere: the measuring - # processes inherit the HIP_VISIBLE_DEVICES it sets instead of each asking HIP in a child process of its own. - hide_integrated_gpus_on_rocm_windows(InvokeAILogger.get_logger(LOGGER_NAME)) - - print(f"{len(points)} forwards, one fresh process each") - lines: list[dict[str, Any]] = [] - with args.out.open("a", encoding="utf-8") as out: - for point in points: - line = _run(point, args.alloc_conf, args.force) - if "raw" in line and not any("raw" in earlier for earlier in lines): - print(_runtime_header(line["raw"]["runtime"], line["platform"])) - if not lines: - print(f"{_COLUMNS} {'demand GiB':>10} {'cold':>8} {'warm':>8}") - record = file_record(line) - if record is not None: - out.write(json.dumps(record) + "\n") - out.flush() - print(_row(line), flush=True) - lines.append(line) + fresh_points.check_alloc_conf(parser, args.alloc_conf) + lines = fresh_points.measure_points( + points, + script=Path(__file__).resolve(), + out=args.out, + alloc_conf=args.alloc_conf, + force=args.force, + logger_name=LOGGER_NAME, + columns=_COLUMNS, + head=_head, + ) print() if args.mode == "fit": _summarize_fit(lines, args.text_tokens) else: _summarize_checks(lines) - statuses = [line["status"] for line in lines] - counts = ", ".join(f"{statuses.count(status)} {status}" for status in ("point", "invalid", "skipped", "failed")) - print(f"{counts}; the points appended to {args.out}") + fresh_points.print_counts(lines, args.out) if __name__ == "__main__": diff --git a/scripts/working_memory/fresh_points.py b/scripts/working_memory/fresh_points.py new file mode 100644 index 00000000000..104d2438553 --- /dev/null +++ b/scripts/working_memory/fresh_points.py @@ -0,0 +1,287 @@ +"""The plumbing every fresh-process calibration script shares: one measuring process per point, and the lines it makes. + +A calibration script (`scripts/calibrate_z_image_working_memory.py`, `scripts/calibrate_flux2_denoise_working_memory.py`) +plans its points, builds its model and inputs, and summarizes; this module runs the rest. The driving process runs each +point as `