Skip to content

feat(gemma4): the sliding family serves an opt in fp8 KV pool - #1012

Merged
FeathBow merged 17 commits into
pegainfer-project:mainfrom
FeathBow:feat/gemma4-fp8-kv
Aug 31, 2026
Merged

feat(gemma4): the sliding family serves an opt in fp8 KV pool#1012
FeathBow merged 17 commits into
pegainfer-project:mainfrom
FeathBow:feat/gemma4-fp8-kv

Conversation

@FeathBow

Copy link
Copy Markdown
Collaborator

Description

Closes #1010

PEGAINFER_KV_FP8=local stores the sliding family's K/V as e4m3 at scale 1.0, halving the decode step's dominant KV read; unset serves byte-identically to before. The reference engine defaults to exactly this scheme for the checkpoint, so the two engines finally read the same bytes per step.

The pool layout carries an element width, the geometry gate converts the backing length by it, and the shared launch header takes the KV element type as a template parameter: a gemma4 translation unit instantiates the two hd256 windowed reads over e4m3 while the bf16 instantiations and bindings stay where they are, and the hd256 prep kernel takes a store type the same way. Wrappers without an fp8 twin refuse a one-byte layout at the operator boundary, and the prefix-cache combination is refused at startup because its page copies index bf16 elements.

Test Env

Single GPU (sm_89, 48 GiB, x86_64).

Verification

Results from the box run on the carve; the accuracy legs decide whether this opens.

  • cargo fmt --all -- --check and git diff --check against main pass; the Gemma 4 release server builds; the qwen3 default build (no gemma4 feature) builds; all-target kernels/Gemma/core Clippy passes with -D warnings; release lib tests pass; the kernel GPU suites pass 21/21.
  • Unset: the 26B checkpoint answers four greedy public /v1/completions requests (31-, 35-, 33- and 1421-token prompts, 64 forced tokens) with bytes identical to main's binary, md5 219de1e3c09c.
  • Startup refusals: PEGAINFER_KV_FP8=local with PEGAINFER_PREFIX_CACHE set exits with "the prefix cache copies pool pages in bf16 element units"; PEGAINFER_KV_FP8=global exits with "supports only "local"".
  • fp8, gates under PEGAINFER_KV_FP8=local: the greedy HF generation gate passes token for token; the ragged-batch row-order gate and the eviction gate pass; the 16K waypoint gate fails at the window-edge waypoint (w1023: top-1 5/9 below backend bar 6/9; every other waypoint within tolerance); the mixed-step-against-serial gate, bit-exact under bf16, diverges under fp8 on the window-crossing partner; the shared-lane lifecycle gate configures the prefix cache and is refused at startup by design.
  • fp8, determinism and composition: the four prompts from two fresh fp8 starts carry one md5; the same prompt at c1 and as four identical concurrent requests differs on both the bf16 and the fp8 pool alike (the engine's known composition dependence, not the pool's); the fp8 texts first diverge from the bf16 texts at the 31st, 24th, 10th and 20th word and stay coherent.
  • Serving A/B on the one binary, bf16 pool against fp8 pool, vllm bench serve random 1024 in / 128 out at 8 / 24 / 64 prompts, two cooled alternating rounds, output tok/s as mean [range]: c1 106.3 [106.0–106.5] → 105.1 [105.0–105.2]; c4 279.8 [279.1–280.4] → 285.0 [284.2–285.7]; c16 595.0 [591.4–598.5] → 629.1 [627.5–630.6]; median TPOT at c16 23.92 → 22.10 ms.

Signed-off-by: Feathbow <feathbow@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe393f7564

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread pegainfer-gemma4/src/serve.rs Outdated
Err(std::env::VarError::NotPresent) => Ok(2),
Ok(value) if value == "local" => {
anyhow::ensure!(
std::env::var_os("PEGAINFER_PREFIX_CACHE").is_none(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat disabled prefix-cache values as disabled

When FP8 is enabled in an environment that explicitly disables caching with PEGAINFER_PREFIX_CACHE=0, off, or an empty string, this raw presence check rejects startup even though parse_prefix_cache_cap in engine.rs normalizes all three spellings to None. This breaks an otherwise supported deployment configuration; base the incompatibility check on the parsed cache capacity rather than whether the variable exists.

Useful? React with 👍 / 👎.

Comment thread pegainfer-gemma4/src/serve.rs Outdated
Comment on lines +658 to +659
fn local_kv_elem_bytes() -> Result<usize> {
match std::env::var("PEGAINFER_KV_FP8") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Document the FP8 serving contract

This introduces a production environment variable, a prefix-cache incompatibility, changed pool memory consumption, and approximate-output behavior without updating docs/models/gemma4/serving.md or the repository's key-env-var list. Operators therefore cannot discover or safely configure the feature from the repository documentation; document the knob, supported value, incompatibilities, memory envelope, and accuracy tradeoff alongside the existing Gemma serving controls.

AGENTS.md reference: AGENTS.md:L176-L178

Useful? React with 👍 / 👎.

…riable

Signed-off-by: Feathbow <feathbow@gmail.com>
Signed-off-by: Feathbow <feathbow@gmail.com>
PagedKvLayout and KvLayout carry KvStorage::{Bf16, E4m3} instead of a
public byte count, so an illegal width is unrepresentable and the
geometry gate dispatches on the enum rather than defaulting everything
that is not exactly one byte to bf16. The three fp8 dispatch sites name
the entry point that actually ran when they fail. PEGAINFER_KV_FP8
parses once in the engine beside the other knobs, before the checkpoint
loads, and GemmaServe receives the storage as a parameter; the second
raw-env interpretation in serve.rs is gone, and the enabled-prefix-cache
refusal keeps its message.

Signed-off-by: Feathbow <feathbow@gmail.com>
…inned

Signed-off-by: Feathbow <feathbow@gmail.com>
…gine's own floor

Signed-off-by: Feathbow <feathbow@gmail.com>
@FeathBow
FeathBow force-pushed the feat/gemma4-fp8-kv branch from a40bef6 to 552de78 Compare August 30, 2026 20:49
Since the knob parse moved into the engine, stack_with pinned Bf16, so
PEGAINFER_KV_FP8=local stopped reaching the broad checkpoint gates: the
runner fp8 arm silently measured bf16. The stack now asks the engine
parsed knob, prints the storage it built, and the runner banner echoes
the variable when set.

Signed-off-by: Feathbow <feathbow@gmail.com>
…rossing case

The floor gate ran one prompt with no degeneracy guard. The body moves
into agreement_case; the gate runs the window-edge prompt at stride 2
and the 4096 prompt truncated to 2048 positions at stride 8, teacher
forcing well past the window, and every case demands a non-degenerate
floor before comparing fp8 against it.

Signed-off-by: Feathbow <feathbow@gmail.com>
The read instruments used an all-zero Q over per-page-identical values,
so equal scores hid wrong K reads and wrong page picks, and only the
unbounded window ran. The pools now carry distinct e4m3-representable
values under a non-zero Q (identical post-load floats keep the bf16
comparison bitwise), a finite-window arm proves the mask live, the
decode prep store lands its exact bytes, a varied-value geometry probe
joins the constant one, and the core pool constructor byte capacity is
pinned.

Signed-off-by: Feathbow <feathbow@gmail.com>
The top-1 bar asserted inside gate_waypoint, so the first failing case
hid every case behind it - under the fp8 pool the w1023 failure
silenced the other seven. The shortfall now joins the collected
failures beside the tolerance overage, both batches collect before the
one verdict, and the per-case line still prints, so a failing run
shows the whole table. The structural asserts (fixture positions,
shifted coverage, page accounting) stay in place.

Signed-off-by: Feathbow <feathbow@gmail.com>
…table

The varied pool byte pattern had period 120 against a page step of
1024, so pages aliased every 15 pages and the 151-page probe target
was byte-identical to page 0 - a wrong page-list base passed anyway.
Each page now draws a distinct e4m3 code offset (aliasing would need
254 pages), the packed prefix table is reversed so a logical-index
confusion changes what is read, and the probe asserts the target page
differs from every other page before comparing.

Signed-off-by: Feathbow <feathbow@gmail.com>
…suite joins the runner

SERVING_KNOBS omitted PEGAINFER_KV_FP8, so an operator shell fp8 value
could steer lifecycle tests into the fp8 refusals. The runner now owns
the kernels integration binary the way it owns everything else: a
manifest of its seven tests, a membership check against the binary own
listing, and an execution arm that demands the device
(PEGAINFER_REQUIRE_GPU=1) so a missing GPU fails instead of skipping.

Signed-off-by: Feathbow <feathbow@gmail.com>
mixed_step_matches_serial demands bit-exact equality between admission
schedules and prefix_restore_matches_cold_path exercises page copies
that are bf16-only, but both built their stacks through the
env-honoring constructor, so an fp8 arm turned contract statements
into permanent failures. Both now pin bf16 and name where the fp8
pool is judged instead; the same-pool invariance gates stay
storage-honoring.

Signed-off-by: Feathbow <feathbow@gmail.com>
Signed-off-by: Feathbow <feathbow@gmail.com>
A set-but-empty PEGAINFER_KV_FP8 slipped past the ambient refusal while
the production parser refuses it, and a set-but-empty
PEGAINFER_GATE_STORAGE silently became bf16 although only unset, bf16
and fp8 are accepted. Presence now checks +x and the default applies
only when the variable is truly unset, so an empty value dies.

Signed-off-by: Feathbow <feathbow@gmail.com>
…eterminism

The determinism argument behind the cross-shape floor rested on a
serving-probe observation. The gate now replays the bf16 incremental
arm on its own prompt and schedule and asserts the replay agrees at
every position, so the run-to-run baseline is measured where the
floor is used: deterministic, hence degenerate for judging a lossy
storage.

Signed-off-by: Feathbow <feathbow@gmail.com>
… invariants

Pinning the bit-exact gates to bf16 left mixed_prefill_decode_step
unexercised under e4m3. A new gate builds the e4m3 stack explicitly
and drives the same mixed machinery through the plain and the
window-crossing walks, asserting structure instead of bit equality:
finite mixed-step logits, every lane reaching exactly its budget, and
the window-family page accounting holding right after the mixed step
that releases the front. Registered in the admission set and the fp8
profile; no tolerance constants.

Signed-off-by: Feathbow <feathbow@gmail.com>
@FeathBow
FeathBow merged commit bb34a29 into pegainfer-project:main Aug 31, 2026
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gemma4: the sliding family stores KV at twice the width its window needs

1 participant