fold-attention 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. fold_attention-0.1.0/PKG-INFO +124 -0
  2. fold_attention-0.1.0/README.md +106 -0
  3. fold_attention-0.1.0/pyproject.toml +62 -0
  4. fold_attention-0.1.0/pyproject.toml.orig +74 -0
  5. fold_attention-0.1.0/src/fold_attention/__init__.py +16 -0
  6. fold_attention-0.1.0/src/fold_attention/backward/__init__.py +6 -0
  7. fold_attention-0.1.0/src/fold_attention/backward/ablation.py +29 -0
  8. fold_attention-0.1.0/src/fold_attention/backward/grid.py +100 -0
  9. fold_attention-0.1.0/src/fold_attention/backward/kernel.py +1811 -0
  10. fold_attention-0.1.0/src/fold_attention/backward/launch.py +462 -0
  11. fold_attention-0.1.0/src/fold_attention/backward/plan.py +227 -0
  12. fold_attention-0.1.0/src/fold_attention/backward/postprocess.py +344 -0
  13. fold_attention-0.1.0/src/fold_attention/backward/preprocess.py +346 -0
  14. fold_attention-0.1.0/src/fold_attention/backward/scheduler.py +126 -0
  15. fold_attention-0.1.0/src/fold_attention/decode/__init__.py +53 -0
  16. fold_attention-0.1.0/src/fold_attention/decode/cache.py +443 -0
  17. fold_attention-0.1.0/src/fold_attention/decode/combine.py +210 -0
  18. fold_attention-0.1.0/src/fold_attention/decode/config.py +206 -0
  19. fold_attention-0.1.0/src/fold_attention/decode/device.py +780 -0
  20. fold_attention-0.1.0/src/fold_attention/decode/front.py +722 -0
  21. fold_attention-0.1.0/src/fold_attention/decode/heuristics.py +853 -0
  22. fold_attention-0.1.0/src/fold_attention/decode/kernel.py +1562 -0
  23. fold_attention-0.1.0/src/fold_attention/decode/launch.py +1362 -0
  24. fold_attention-0.1.0/src/fold_attention/decode/packed.py +1081 -0
  25. fold_attention-0.1.0/src/fold_attention/decode/ptx.py +524 -0
  26. fold_attention-0.1.0/src/fold_attention/decode/quant.py +204 -0
  27. fold_attention-0.1.0/src/fold_attention/decode/reference.py +591 -0
  28. fold_attention-0.1.0/src/fold_attention/decode/rows.py +184 -0
  29. fold_attention-0.1.0/src/fold_attention/decode/wide.py +902 -0
  30. fold_attention-0.1.0/src/fold_attention/decode/write.py +1009 -0
  31. fold_attention-0.1.0/src/fold_attention/interface.py +173 -0
  32. fold_attention-0.1.0/src/fold_attention/kv_cache.py +963 -0
  33. fold_attention-0.1.0/src/fold_attention/ptx.py +123 -0
  34. fold_attention-0.1.0/src/fold_attention/utils.py +123 -0
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.3
2
+ Name: fold-attention
3
+ Version: 0.1.0
4
+ Summary: FoldAttention declares softmax’s reference before execution, making attention additive and enabling up to 3.09× faster H100 decode and deterministic backward faster than nondeterministic kernels.
5
+ Author: Sriman Achanta
6
+ Author-email: Sriman Achanta <srimanachanta@gmail.com>
7
+ Requires-Dist: flash-attn-4>=4.0.0b31
8
+ Requires-Dist: numpy>=2.5.3
9
+ Requires-Dist: nvidia-cutlass-dsl>=4.7.1
10
+ Requires-Dist: quack-kernels>=0.6.5
11
+ Requires-Dist: torch>=2.14.0
12
+ Requires-Dist: flash-attn-4[cu13]>=4.0.0b31 ; extra == 'cu13'
13
+ Requires-Dist: nvidia-cutlass-dsl[cu13]>=4.7.1 ; extra == 'cu13'
14
+ Requires-Dist: quack-kernels[cu13]>=0.6.5 ; extra == 'cu13'
15
+ Requires-Python: >=3.12
16
+ Provides-Extra: cu13
17
+ Description-Content-Type: text/markdown
18
+
19
+ # FoldAttention
20
+
21
+ <p align="center">
22
+ <img src="assets/hero.png" alt="FoldAttention overview: declared-reference softmax, decode speedup, and deterministic backward throughput">
23
+ </p>
24
+
25
+ FoldAttention declares softmax's reference before execution. Final weights
26
+ make decode contributions additive, while a shared integer grid makes the
27
+ backward deterministic without serializing its reductions.
28
+
29
+ ## Usage
30
+
31
+ Install FoldAttention from PyPI:
32
+
33
+ ```bash
34
+ pip install fold-attention
35
+ ```
36
+
37
+ Install the CUDA 13 dependencies with the `cu13` extra:
38
+
39
+ ```bash
40
+ pip install "fold-attention[cu13]"
41
+ ```
42
+
43
+ FoldAttention requires Python 3.12 or newer and an NVIDIA SM90 GPU.
44
+
45
+ ## Code usage
46
+
47
+ ### Training
48
+
49
+ `fold_attn_func` follows FlashAttention's `(batch, seqlen, heads, head_dim)`
50
+ layout. Keys and values may use fewer heads for GQA or MQA.
51
+
52
+ ```python
53
+ from fold_attention import fold_attn_func
54
+
55
+ out = fold_attn_func(q, k, v, causal=True)
56
+ out.backward(dout)
57
+ ```
58
+
59
+ The forward uses FlashAttention-4. The FoldAttention backward produces
60
+ bit-identical gradients across repeated runs, batching, and variable-length
61
+ packing.
62
+
63
+ For packed self-attention, pass `(total_tokens, heads, head_dim)` tensors and
64
+ a CUDA `int32` cumulative-length vector:
65
+
66
+ ```python
67
+ from fold_attention import fold_attn_varlen_func
68
+
69
+ out = fold_attn_varlen_func(q, k, v, cu_seqlens, causal=True)
70
+ ```
71
+
72
+ ### Decode
73
+
74
+ `FoldKVCache` owns one layer's paged cache. `prefill` runs FlashAttention-4
75
+ and writes the cache. `fold_attn_with_kvcache` optionally appends one token per
76
+ request, then attends over the updated cache.
77
+
78
+ ```python
79
+ from fold_attention import FoldKVCache, fold_attn_with_kvcache
80
+
81
+ cache = FoldKVCache(
82
+ batch=batch_size,
83
+ n_heads=n_heads,
84
+ n_kv_heads=n_kv_heads,
85
+ head_dim=head_dim,
86
+ max_len=max_len,
87
+ depth=16,
88
+ )
89
+
90
+ prompt_out = cache.prefill(q, k, v, cu_seqlens)
91
+ step_out = fold_attn_with_kvcache(q_step, cache, k_step, v_step)
92
+ ```
93
+
94
+ Set `depth=None` for dense decode. A finite depth cuts low-weight keys while
95
+ retaining their normalization mass. Set `v8=True` to store values in two E4M3
96
+ planes.
97
+
98
+ ## Benchmarks
99
+
100
+ ### Decode speed and accuracy
101
+
102
+ Latency against FP32-relative error on seven real-model generations. Each
103
+ FoldAttention curve sweeps the decode depth.
104
+
105
+ <p align="center">
106
+ <img src="assets/decode-pareto.png" alt="Decode latency against FP32-relative error on seven real-model generations">
107
+ </p>
108
+
109
+ ### Deterministic backward
110
+
111
+ Causal backward throughput across MHA and GQA shapes. The rows below each
112
+ panel report FoldAttention throughput relative to the fastest deterministic
113
+ and nondeterministic kernel.
114
+
115
+ <p align="center">
116
+ <img src="assets/backward-throughput.png" alt="Causal attention backward throughput on MHA and GQA shapes">
117
+ </p>
118
+
119
+ The benchmark suite and measurement protocol are documented in
120
+ [`benchmarks/README.md`](benchmarks/README.md).
121
+
122
+ ## License
123
+
124
+ FoldAttention is released under the [Apache License 2.0](LICENSE).
@@ -0,0 +1,106 @@
1
+ # FoldAttention
2
+
3
+ <p align="center">
4
+ <img src="assets/hero.png" alt="FoldAttention overview: declared-reference softmax, decode speedup, and deterministic backward throughput">
5
+ </p>
6
+
7
+ FoldAttention declares softmax's reference before execution. Final weights
8
+ make decode contributions additive, while a shared integer grid makes the
9
+ backward deterministic without serializing its reductions.
10
+
11
+ ## Usage
12
+
13
+ Install FoldAttention from PyPI:
14
+
15
+ ```bash
16
+ pip install fold-attention
17
+ ```
18
+
19
+ Install the CUDA 13 dependencies with the `cu13` extra:
20
+
21
+ ```bash
22
+ pip install "fold-attention[cu13]"
23
+ ```
24
+
25
+ FoldAttention requires Python 3.12 or newer and an NVIDIA SM90 GPU.
26
+
27
+ ## Code usage
28
+
29
+ ### Training
30
+
31
+ `fold_attn_func` follows FlashAttention's `(batch, seqlen, heads, head_dim)`
32
+ layout. Keys and values may use fewer heads for GQA or MQA.
33
+
34
+ ```python
35
+ from fold_attention import fold_attn_func
36
+
37
+ out = fold_attn_func(q, k, v, causal=True)
38
+ out.backward(dout)
39
+ ```
40
+
41
+ The forward uses FlashAttention-4. The FoldAttention backward produces
42
+ bit-identical gradients across repeated runs, batching, and variable-length
43
+ packing.
44
+
45
+ For packed self-attention, pass `(total_tokens, heads, head_dim)` tensors and
46
+ a CUDA `int32` cumulative-length vector:
47
+
48
+ ```python
49
+ from fold_attention import fold_attn_varlen_func
50
+
51
+ out = fold_attn_varlen_func(q, k, v, cu_seqlens, causal=True)
52
+ ```
53
+
54
+ ### Decode
55
+
56
+ `FoldKVCache` owns one layer's paged cache. `prefill` runs FlashAttention-4
57
+ and writes the cache. `fold_attn_with_kvcache` optionally appends one token per
58
+ request, then attends over the updated cache.
59
+
60
+ ```python
61
+ from fold_attention import FoldKVCache, fold_attn_with_kvcache
62
+
63
+ cache = FoldKVCache(
64
+ batch=batch_size,
65
+ n_heads=n_heads,
66
+ n_kv_heads=n_kv_heads,
67
+ head_dim=head_dim,
68
+ max_len=max_len,
69
+ depth=16,
70
+ )
71
+
72
+ prompt_out = cache.prefill(q, k, v, cu_seqlens)
73
+ step_out = fold_attn_with_kvcache(q_step, cache, k_step, v_step)
74
+ ```
75
+
76
+ Set `depth=None` for dense decode. A finite depth cuts low-weight keys while
77
+ retaining their normalization mass. Set `v8=True` to store values in two E4M3
78
+ planes.
79
+
80
+ ## Benchmarks
81
+
82
+ ### Decode speed and accuracy
83
+
84
+ Latency against FP32-relative error on seven real-model generations. Each
85
+ FoldAttention curve sweeps the decode depth.
86
+
87
+ <p align="center">
88
+ <img src="assets/decode-pareto.png" alt="Decode latency against FP32-relative error on seven real-model generations">
89
+ </p>
90
+
91
+ ### Deterministic backward
92
+
93
+ Causal backward throughput across MHA and GQA shapes. The rows below each
94
+ panel report FoldAttention throughput relative to the fastest deterministic
95
+ and nondeterministic kernel.
96
+
97
+ <p align="center">
98
+ <img src="assets/backward-throughput.png" alt="Causal attention backward throughput on MHA and GQA shapes">
99
+ </p>
100
+
101
+ The benchmark suite and measurement protocol are documented in
102
+ [`benchmarks/README.md`](benchmarks/README.md).
103
+
104
+ ## License
105
+
106
+ FoldAttention is released under the [Apache License 2.0](LICENSE).
@@ -0,0 +1,62 @@
1
+ [project]
2
+ name = "fold-attention"
3
+ version = "0.1.0"
4
+ description = "FoldAttention declares softmax’s reference before execution, making attention additive and enabling up to 3.09× faster H100 decode and deterministic backward faster than nondeterministic kernels."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "flash-attn-4>=4.0.0b31",
9
+ "numpy>=2.5.3",
10
+ "nvidia-cutlass-dsl>=4.7.1",
11
+ "quack-kernels>=0.6.5",
12
+ "torch>=2.14.0",
13
+ ]
14
+
15
+ [[project.authors]]
16
+ name = "Sriman Achanta"
17
+ email = "srimanachanta@gmail.com"
18
+
19
+ [project.optional-dependencies]
20
+ cu13 = [
21
+ "flash-attn-4[cu13]>=4.0.0b31",
22
+ "nvidia-cutlass-dsl[cu13]>=4.7.1",
23
+ "quack-kernels[cu13]>=0.6.5",
24
+ ]
25
+
26
+ [dependency-groups]
27
+ dev = ["pytest>=8.0"]
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.12.10,<0.13.0"]
31
+ build-backend = "uv_build"
32
+
33
+ [tool.uv.sources.torch]
34
+ index = "pytorch-cu130"
35
+
36
+ [[tool.uv.index]]
37
+ name = "pytorch-cu130"
38
+ url = "https://download.pytorch.org/whl/cu130"
39
+ explicit = true
40
+
41
+ [tool.ty.environment]
42
+ python = ".venv"
43
+ extra-paths = [".venv/lib/python3.14/site-packages/nvidia_cutlass_dsl/dsl_packages"]
44
+
45
+ [tool.ty.analysis]
46
+ allowed-unresolved-imports = ["cuda.bindings.**"]
47
+ replace-imports-with-any = ["cutlass._mlir.**"]
48
+
49
+ [tool.ruff]
50
+ line-length = 100
51
+ target-version = "py312"
52
+
53
+ [tool.ruff.lint]
54
+ ignore = [
55
+ "C408",
56
+ "FURB136",
57
+ "PLR1730",
58
+ "SIM102",
59
+ "SIM108",
60
+ "SIM113",
61
+ "SIM114",
62
+ ]
@@ -0,0 +1,74 @@
1
+ [project]
2
+ name = "fold-attention"
3
+ version = "0.1.0"
4
+ description = "FoldAttention declares softmax’s reference before execution, making attention additive and enabling up to 3.09× faster H100 decode and deterministic backward faster than nondeterministic kernels."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Sriman Achanta", email = "srimanachanta@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "flash-attn-4>=4.0.0b31",
12
+ "numpy>=2.5.3",
13
+ "nvidia-cutlass-dsl>=4.7.1",
14
+ "quack-kernels>=0.6.5",
15
+ "torch>=2.14.0",
16
+ ]
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "pytest>=8.0",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ cu13 = [
25
+ "flash-attn-4[cu13]>=4.0.0b31",
26
+ "nvidia-cutlass-dsl[cu13]>=4.7.1",
27
+ "quack-kernels[cu13]>=0.6.5",
28
+ ]
29
+
30
+ [build-system]
31
+ requires = ["uv_build>=0.12.10,<0.13.0"]
32
+ build-backend = "uv_build"
33
+
34
+ [tool.uv.sources]
35
+ torch = { index = "pytorch-cu130" }
36
+
37
+ [[tool.uv.index]]
38
+ name = "pytorch-cu130"
39
+ url = "https://download.pytorch.org/whl/cu130"
40
+ explicit = true
41
+
42
+ [tool.ty.environment]
43
+ python = ".venv"
44
+ extra-paths = [
45
+ ".venv/lib/python3.14/site-packages/nvidia_cutlass_dsl/dsl_packages"
46
+ ]
47
+
48
+ [tool.ty.analysis]
49
+ allowed-unresolved-imports = [
50
+ "cuda.bindings.**",
51
+ ]
52
+ replace-imports-with-any = ["cutlass._mlir.**"]
53
+
54
+ [tool.ruff]
55
+ line-length = 100
56
+ target-version = "py312"
57
+
58
+ [tool.ruff.lint]
59
+ ignore = [
60
+ # keyword dicts read as the calls they feed
61
+ "C408",
62
+ # Rewrites that break or change CuTe DSL tracing. A condition on a traced
63
+ # value has no Python bool, so builtin `min`/`max` (PLR1730, FURB136) and
64
+ # a ternary (SIM108) fail to compile; `enumerate` hides a `cutlass.range`
65
+ # loop from the DSL's loop rewrite (SIM113); collapsing or merging `if`
66
+ # statements (SIM102, SIM114) changes the regions a dynamic condition
67
+ # traces into.
68
+ "FURB136",
69
+ "PLR1730",
70
+ "SIM102",
71
+ "SIM108",
72
+ "SIM113",
73
+ "SIM114",
74
+ ]
@@ -0,0 +1,16 @@
1
+ """FoldAttention: softmax attention whose cross-CTA reductions do not depend
2
+ on the order work runs in.
3
+
4
+ Training uses FlashAttention-4's forward and an SM90 backward whose dQ, dK and
5
+ dV sums are integer or fixed-order reductions, so gradients are the same bits
6
+ on every run and for every batching of a request. Serving decodes over a
7
+ quantised paged cache against a static per-row reference, so split-KV,
8
+ shared-prefix and speculative partials combine by plain addition.
9
+ """
10
+
11
+ from .interface import fold_attn_func, fold_attn_varlen_func, fold_attn_with_kvcache
12
+ from .kv_cache import FoldKVCache
13
+
14
+ __version__ = "0.1.0"
15
+
16
+ __all__ = ["FoldKVCache", "fold_attn_func", "fold_attn_varlen_func", "fold_attn_with_kvcache"]
@@ -0,0 +1,6 @@
1
+ """The attention backward with order-free cross-CTA reductions."""
2
+
3
+ from .launch import prepare_backward
4
+ from .plan import Plan, plan_dense, plan_varlen, tile_config
5
+
6
+ __all__ = ["Plan", "plan_dense", "plan_varlen", "prepare_backward", "tile_config"]
@@ -0,0 +1,29 @@
1
+ """The backward with one mechanism changed, for measuring what it buys.
2
+
3
+ Each variant is the shipped kernel (`prepare_backward`) with one choice
4
+ replaced and the rest as it is:
5
+
6
+ - `fp32_dq=True`: dQ's partials summed as fp32 by `cp.reduce.async.bulk
7
+ .add.f32` in the order they land, as FlashAttention sums them. It skips the
8
+ rounding and is not deterministic. P then carries no grid exponent, whose
9
+ subtraction rounds the softmax's argument, so dK and dV move in their last
10
+ bits too.
11
+ - `persistent=False`: a dense call runs one CTA per work tile under
12
+ `SingleTileScheduler` instead of the persistent work list, with no
13
+ canonical dK/dV records.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from .launch import BackwardLaunch, _prepare
19
+
20
+
21
+ def prepare_backward_variant(
22
+ q, k, v, o, do, lse, *, causal, fp32_dq=False, persistent=True, **kwargs
23
+ ) -> BackwardLaunch:
24
+ """`prepare_backward` with dQ summed as fp32 (`fp32_dq`) and the dense
25
+ scheduler `persistent`; the other keyword arguments are
26
+ `prepare_backward`'s. The defaults are the shipped kernel."""
27
+ return _prepare(
28
+ q, k, v, o, do, lse, causal=causal, dq_fp32=fp32_dq, persistent=persistent, **kwargs
29
+ )
@@ -0,0 +1,100 @@
1
+ """The backward's integer grids, derived from bounds the kernels compute.
2
+
3
+ Every kernel that quantises or reconstructs a partial evaluates the same bound
4
+ with the same rounded instructions, so each arrives at the same scale. The
5
+ inputs are the preprocess's maxima (`red.max` over float bits), which do not
6
+ depend on the order CTAs finish in or on anything outside the request.
7
+
8
+ dQ, per (request, KV head): `|sum_j dS_ij K_jd| <= (sum_j |dS_ij|) max|K|`
9
+ and, with P summing to one, `sum_j |dS_ij| <= |dO_i| max_j |V_j| + |D_i|`,
10
+ where `|V_j| <= C_D max|V|` and `C_D` is sqrt(D) rounded up to a power of two.
11
+ dV, on the canonical-record path: `|dV_jd| <= C max_i |dO_id|`, the largest
12
+ element of dO, since the bound is per output component. dK there:
13
+ `|dK_j| <= C (max|dO| C_D max|V| + max|D|) max|Q|`. `C` bounds the column
14
+ mass `sum_{h,i} P_hij`; the only data-free bound is the number of query rows
15
+ `G n`, which is why dK needs a 61-bit grid. Every bound is widened by 2^-6 for
16
+ the bf16 rounding of P and dS.
17
+ """
18
+
19
+ import math
20
+
21
+ import cutlass
22
+ from cutlass import Float32, Int32
23
+
24
+ from ..ptx import f32_rn
25
+
26
+ # stats[b, h_kv, i], float bits, zero before the call: max |K|, max |V|, max
27
+ # ||dO_i|| over the group's rows, max |D_i| over them, and for the record
28
+ # path max |dO_id| over every element and max |Q| over the group
29
+ ST_K, ST_V, ST_DO, ST_DELTA, ST_DOC, ST_Q = 0, 1, 2, 3, 4, 5
30
+ N_STATS = 8
31
+
32
+ DQ_BITS = 30
33
+ DKV_BITS = 61
34
+
35
+
36
+ def root_d(D):
37
+ """sqrt(D) rounded up to a power of two: exact, and a bound."""
38
+ return float(2 ** math.ceil(math.log2(math.sqrt(D))))
39
+
40
+
41
+ def pow2_scale(bound, bits: int) -> Float32:
42
+ """`2^(bits - ceil(log2 (bound (1 + 2^-6))))`, clamped to [2^-100, 2^100].
43
+ A zero bound takes 2^100, since its partials are all zero."""
44
+ b = f32_rn("mul.rn.f32", bound, Float32(1.0 + 2.0**-6))
45
+ bb = b.bitcast(Int32)
46
+ clog = (
47
+ ((bb >> 23) & 0xFF) - 127 + Int32(cutlass.select_((bb & 0x7FFFFF) != 0, Int32(1), Int32(0)))
48
+ )
49
+ sexp = Int32(bits) - clog
50
+ sexp = Int32(cutlass.select_(b > Float32(0.0), sexp, Int32(100)))
51
+ sexp = Int32(cutlass.select_(sexp > 100, Int32(100), sexp))
52
+ sexp = Int32(cutlass.select_(sexp < -100, Int32(-100), sexp))
53
+ return ((sexp + 127) << 23).bitcast(Float32)
54
+
55
+
56
+ def inv_pow2(scale) -> Float32:
57
+ """`1 / scale` for a power of two, from its bits."""
58
+ return (Int32(0x7F000000) - scale.bitcast(Int32)).bitcast(Float32)
59
+
60
+
61
+ def log2_pow2(scale) -> Float32:
62
+ """The exponent of a power of two, as a float."""
63
+ return ((scale.bitcast(Int32) >> 23) - Int32(127)).to(Float32)
64
+
65
+
66
+ def load_stats(mStats, batch_idx, head_kv, which=range(N_STATS)):
67
+ """The maxima `which` of one (request, KV head), by index."""
68
+ return {i: mStats[batch_idx, head_kv, i].bitcast(Float32) for i in which}
69
+
70
+
71
+ def dq_scale(mStats, batch_idx, head_kv, cd: float) -> Float32:
72
+ """dQ's scale for one (request, KV head).
73
+
74
+ A constant exponent is free here: `P 2^s` is `exp2(c S - lse + s)`, so
75
+ the softmax's own exponent carries it and the dQ partials leave the GEMM
76
+ on the grid already."""
77
+ st = load_stats(mStats, batch_idx, head_kv, (ST_K, ST_V, ST_DO, ST_DELTA))
78
+ s = f32_rn(
79
+ "add.rn.f32",
80
+ f32_rn("mul.rn.f32", st[ST_DO], f32_rn("mul.rn.f32", st[ST_V], Float32(cd))),
81
+ st[ST_DELTA],
82
+ )
83
+ return pow2_scale(f32_rn("mul.rn.f32", s, st[ST_K]), DQ_BITS)
84
+
85
+
86
+ def dv_scale(st, rows, bits: int) -> Float32:
87
+ """dV's scale from `load_stats`, for `rows` query rows bounding the column
88
+ mass."""
89
+ return pow2_scale(f32_rn("mul.rn.f32", rows, st[ST_DOC]), bits)
90
+
91
+
92
+ def dk_scale(st, rows, cd: float, bits: int) -> Float32:
93
+ """dK's scale from `load_stats`, for `rows` query rows bounding the column
94
+ mass."""
95
+ s = f32_rn(
96
+ "add.rn.f32",
97
+ f32_rn("mul.rn.f32", st[ST_DO], f32_rn("mul.rn.f32", st[ST_V], Float32(cd))),
98
+ st[ST_DELTA],
99
+ )
100
+ return pow2_scale(f32_rn("mul.rn.f32", rows, f32_rn("mul.rn.f32", s, st[ST_Q])), bits)