From b913d26babc2c7010361344381b85a5bf18efedc Mon Sep 17 00:00:00 2001 From: Ju-yeong Ji Date: Fri, 23 Oct 2026 14:24:39 +0900 Subject: [PATCH 01/22] Fix model selector overlap --- docs/javascripts/model-switcher.js | 31 +++++++++++++++++++++++++++++- docs/stylesheets/motion.css | 4 ++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/docs/javascripts/model-switcher.js b/docs/javascripts/model-switcher.js index dd4209d..acc35ba 100644 --- a/docs/javascripts/model-switcher.js +++ b/docs/javascripts/model-switcher.js @@ -107,6 +107,30 @@ } }; + const positionMenu = (picker) => { + const menu = picker.querySelector(".page-context__picker-menu"); + const strip = picker.closest(".page-context"); + if (!menu || menu.hidden || !strip) return; + menu.style.left = ""; + menu.style.right = ""; + const stripRect = strip.getBoundingClientRect(); + const pickerRect = picker.getBoundingClientRect(); + const menuRect = menu.getBoundingClientRect(); + if (menuRect.right > stripRect.right) { + if (pickerRect.right - menuRect.width >= stripRect.left) { + menu.style.left = "auto"; + menu.style.right = "0"; + } else { + const offset = Math.max( + stripRect.left - pickerRect.left, + stripRect.right - pickerRect.left - menuRect.width + ); + menu.style.left = `${offset}px`; + menu.style.right = "auto"; + } + } + }; + const closePicker = (picker, returnFocus = false) => { const trigger = picker.querySelector(".page-context__picker-trigger"); const menu = picker.querySelector(".page-context__picker-menu"); @@ -209,6 +233,7 @@ document.querySelectorAll(".page-context__picker").forEach((other) => closePicker(other)); menu.hidden = !opening; trigger.setAttribute("aria-expanded", String(opening)); + if (opening) positionMenu(picker); }); trigger.addEventListener("keydown", (event) => { if (event.key === "Escape") { @@ -220,6 +245,7 @@ event.preventDefault(); menu.hidden = false; trigger.setAttribute("aria-expanded", "true"); + positionMenu(picker); const options = [...menu.querySelectorAll("[role=option]")]; (event.key === "ArrowDown" ? options[0] : options.at(-1))?.focus(); }); @@ -371,7 +397,10 @@ setTimeout(initSearchModalDefaults, 150); } }); - window.addEventListener("resize", syncTabIndicator); + window.addEventListener("resize", () => { + syncTabIndicator(); + document.querySelectorAll(".page-context__picker").forEach(positionMenu); + }); const DEFAULT_SEARCH_TOPICS = [ { diff --git a/docs/stylesheets/motion.css b/docs/stylesheets/motion.css index 2526c0b..63c2074 100644 --- a/docs/stylesheets/motion.css +++ b/docs/stylesheets/motion.css @@ -44,11 +44,11 @@ .md-content__inner, .gemma-stage { - animation: gemmaPageReveal 260ms var(--gemma-ease) both; + animation: gemmaPageReveal 260ms var(--gemma-ease) backwards; } .md-sidebar--primary .md-nav--primary > .md-nav__list { - animation: gemmaPageReveal 220ms var(--gemma-ease) both; + animation: gemmaPageReveal 220ms var(--gemma-ease) backwards; } /* ------------------------------------------------------------------- From 2f9f6549b9a36f6c51ee03dea1f7a01ee39c7af5 Mon Sep 17 00:00:00 2001 From: Ju-yeong Ji Date: Fri, 23 Oct 2026 15:39:35 +0900 Subject: [PATCH 02/22] Fix nested tripple-backtick code output on notebook --- docs/get-started/capabilities/image.md | 14 +++++------ scripts/lint_docs.py | 33 ++++++++++++++++---------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/docs/get-started/capabilities/image.md b/docs/get-started/capabilities/image.md index 5e72819..8ffc8ab 100644 --- a/docs/get-started/capabilities/image.md +++ b/docs/get-started/capabilities/image.md @@ -320,16 +320,16 @@ output = vqa_pipe(messages, return_full_text=False, generate_kwargs=gen_kwargs) print(output[0]['generated_text']) draw_results(output[0]['generated_text']) -``` +``` -```{ .no-copy .output } +```` { .no-copy .output } ```json [ {"box_2d": [243, 252, 956, 415], "label": "person"}, {"box_2d": [356, 606, 654, 802], "label": "cat"} ] ``` -``` +```` ![png](../../assets/capabilities/output_b041486b_1.png) @@ -409,14 +409,14 @@ print(output[0]['generated_text']) draw_results(output[0]['generated_text']) ``` -```{ .no-copy .output } +```` { .no-copy .output } ```json [ {"box_2d": [454, 871, 533, 923], "label": "person"}, {"box_2d": [551, 316, 665, 628], "label": "car"} ] ``` -``` +```` ![png](../../assets/capabilities/output_bc2920e6_1.png) @@ -506,7 +506,7 @@ plt.tight_layout() plt.show() ``` -```{ .no-copy .output } +```` { .no-copy .output } Budget Size: 70 # of Image Tokens: 64 [transformers] You seem to be using the pipelines sequentially on GPU. In order to maximize efficiency please use a dataset @@ -551,7 +551,7 @@ Budget Size: 560 {"box_2d": [404, 867, 520, 925], "label": "person"} ] ``` -``` +```` ![png](../../assets/capabilities/output_76f17518_3.png) diff --git a/scripts/lint_docs.py b/scripts/lint_docs.py index 01e81da..0cda6c3 100644 --- a/scripts/lint_docs.py +++ b/scripts/lint_docs.py @@ -231,23 +231,26 @@ def load_context() -> Context: return Context(pages=load_pages(), nav_entries=list(walk_nav(nav)), config=config) +FENCE_OPEN = re.compile(r"^(\s*)(`{3,}|~{3,})(.*)$") + + def iter_code_blocks(page: Page) -> Iterator[tuple[int, str, str]]: """Yield (start line, info string, block content) for each fenced code block.""" - fence = re.compile(r"^(\s*)```(.*)$") open_at: int | None = None + active_fence = "" info = "" buf: list[str] = [] for lineno, line in page.body_lines(): - m = fence.match(line) - if not m: - if open_at is not None: - buf.append(line) - continue + m = FENCE_OPEN.match(line) if open_at is None: - open_at, info, buf = lineno, m.group(2).strip(), [] - else: + if m: + open_at, active_fence, info, buf = lineno, m.group(2), m.group(3).strip(), [] + continue + if m and m.group(2) == active_fence and not m.group(3).strip(): yield open_at, info, "\n".join(buf) open_at = None + else: + buf.append(line) INLINE_CODE = re.compile(r"`+[^`]*`+") @@ -264,13 +267,17 @@ def strip_inline_code(line: str) -> str: def outside_code(page: Page) -> Iterator[tuple[int, str]]: """Yield body lines that are not inside a fenced code block, with code spans blanked.""" - in_fence = False + active_fence: str | None = None for lineno, line in page.body_lines(): - if line.lstrip().startswith("```"): - in_fence = not in_fence - continue - if not in_fence: + m = FENCE_OPEN.match(line) + if active_fence is None: + if m: + active_fence = m.group(2) + continue yield lineno, strip_inline_code(line) + elif m and m.group(2) == active_fence and not m.group(3).strip(): + active_fence = None + continue # --- Checks ----------------------------------------------------------------------------------- From 07ff8ae29de54a6d05c6202508bfb0a8e3bc2713 Mon Sep 17 00:00:00 2001 From: Ju-yeong Ji Date: Mon, 26 Oct 2026 10:04:51 +0900 Subject: [PATCH 03/22] Fix model-swap --- data/models.yml | 22 ++++++++++--- docs/deployment/llamacpp.md | 36 +++++++-------------- docs/get-started/index.md | 12 +++---- docs/javascripts/model-data.js | 26 +++++++++++----- docs/javascripts/model-switcher.js | 31 +++++++++++------- scripts/lint_docs.py | 50 ++++++++++++++++++++++++++---- scripts/notebooks.py | 15 +++++---- 7 files changed, 127 insertions(+), 65 deletions(-) diff --git a/data/models.yml b/data/models.yml index c5c12dd..c22dab7 100644 --- a/data/models.yml +++ b/data/models.yml @@ -304,8 +304,10 @@ gemma4: hf: google/gemma-4-E2B-it hf_base: google/gemma-4-E2B hf_qat: google/gemma-4-E2B-it-qat-w4a16-ct - gguf: google/gemma-4-E2B-it-qat-q4_0-gguf + gguf: ggml-org/gemma-4-E2B-it-GGUF gguf_qat: google/gemma-4-E2B-it-qat-q4_0-gguf + gguf_file: gemma-4-E2B-it-Q4_0.gguf + gguf_mmproj: mmproj-gemma-4-E2B-it-Q8_0.gguf ollama: gemma4:e2b mlx: mlx-community/gemma-4-E2B-it-qat-4bit lmstudio: google/gemma-4-e2b @@ -313,6 +315,7 @@ gemma4: keras_base: gemma4_2b keras_assistant: gemma4_instruct_2b_assistant litert: litert-community/gemma-4-E2B-it-litert-lm + litert_file: gemma-4-E2B-it.litertlm memory: # GB, static weights + ~20% overhead bf16: 11.4 sfp8: 5.7 @@ -335,8 +338,10 @@ gemma4: hf: google/gemma-4-E4B-it hf_base: google/gemma-4-E4B hf_qat: google/gemma-4-E4B-it-qat-w4a16-ct - gguf: google/gemma-4-E4B-it-qat-q4_0-gguf + gguf: ggml-org/gemma-4-E4B-it-GGUF gguf_qat: google/gemma-4-E4B-it-qat-q4_0-gguf + gguf_file: gemma-4-E4B-it-Q4_0.gguf + gguf_mmproj: mmproj-gemma-4-E4B-it-Q8_0.gguf ollama: gemma4:e4b mlx: mlx-community/gemma-4-E4B-it-qat-4bit lmstudio: google/gemma-4-e4b @@ -344,6 +349,7 @@ gemma4: keras_base: gemma4_4b keras_assistant: gemma4_instruct_4b_assistant litert: litert-community/gemma-4-E4B-it-litert-lm + litert_file: gemma-4-E4B-it.litertlm memory: bf16: 17.9 sfp8: 8.9 @@ -366,8 +372,10 @@ gemma4: hf: google/gemma-4-12B-it hf_base: google/gemma-4-12B hf_qat: google/gemma-4-12B-it-qat-w4a16-ct - gguf: google/gemma-4-12B-it-qat-q4_0-gguf + gguf: ggml-org/gemma-4-12B-it-GGUF gguf_qat: google/gemma-4-12B-it-qat-q4_0-gguf + gguf_file: gemma-4-12B-it-Q4_0.gguf + gguf_mmproj: mmproj-gemma-4-12B-it-Q8_0.gguf ollama: gemma4:12b mlx: mlx-community/gemma-4-12B-it-qat-4bit lmstudio: google/gemma-4-12b @@ -391,8 +399,10 @@ gemma4: ids: # verified repo/tag strings; see PLAN.md hf: google/gemma-4-26B-A4B-it hf_base: google/gemma-4-26B-A4B - gguf: google/gemma-4-26B-A4B-it-qat-q4_0-gguf + gguf: ggml-org/gemma-4-26B-A4B-it-GGUF gguf_qat: google/gemma-4-26B-A4B-it-qat-q4_0-gguf + gguf_file: gemma-4-26B-A4B-it-Q4_0.gguf + gguf_mmproj: mmproj-gemma-4-26B-A4B-it-Q8_0.gguf ollama: gemma4:26b mlx: mlx-community/gemma-4-26B-A4B-it-qat-4bit lmstudio: google/gemma-4-26b-a4b @@ -420,8 +430,10 @@ gemma4: hf: google/gemma-4-31B-it hf_base: google/gemma-4-31B hf_qat: google/gemma-4-31B-it-qat-w4a16-ct - gguf: google/gemma-4-31B-it-qat-q4_0-gguf + gguf: ggml-org/gemma-4-31B-it-GGUF gguf_qat: google/gemma-4-31B-it-qat-q4_0-gguf + gguf_file: gemma-4-31B-it-Q4_0.gguf + gguf_mmproj: mmproj-gemma-4-31B-it-Q8_0.gguf ollama: gemma4:31b mlx: mlx-community/gemma-4-31B-it-qat-4bit lmstudio: google/gemma-4-31b diff --git a/docs/deployment/llamacpp.md b/docs/deployment/llamacpp.md index 781101f..1c93499 100644 --- a/docs/deployment/llamacpp.md +++ b/docs/deployment/llamacpp.md @@ -13,41 +13,29 @@ most capable local runtime for Gemma 4 — **image, audio and video input all wo ## Install -=== "macOS / Linux" +=== "curl" ```bash - brew install llama.cpp + curl -LsSf https://llama.app/install.sh | sh ``` -=== "Windows" +=== "powershell" - ```bash - winget install llama.cpp - ``` - -=== "conda" - - ```bash - conda install -c conda-forge llama.cpp - ``` - -=== "Nix" - - ```bash - nix profile install nixpkgs#llama-cpp + ```powershell + irm https://llama.app/install.ps1 | iex ``` ## Run ```bash { .model-swap } -llama-cli -hf ggml-org/gemma-4-E2B-it-GGUF --prompt "Write a poem about the Kraken." +llama cli -hf ggml-org/gemma-4-E2B-it-GGUF --prompt "Write a poem about the Kraken." ``` `-hf` downloads the weights on first use, including the multimodal projector. With a system prompt: ```bash { .model-swap } -llama-cli -hf ggml-org/gemma-4-E2B-it-GGUF -sys "You are Hong Gildong." -p "Who are you?" +llama cli -hf ggml-org/gemma-4-E2B-it-GGUF -sys "You are Hong Gildong." -p "Who are you?" ``` ## Model repositories @@ -64,7 +52,7 @@ For 4-bit, prefer Google's [QAT](../get-started/qat.md) weights — trained with than compressed afterwards: ```bash { .model-swap } -llama-cli -hf google/gemma-4-12B-it-qat-q4_0-gguf -p "Explain PLE in one paragraph" +llama cli -hf google/gemma-4-E2B-it-qat-q4_0-gguf -p "Explain gravity in one paragraph" ``` Available as `google/gemma-4-{E2B,E4B,12B,26B-A4B,31B}-it-qat-q4_0-gguf`. @@ -72,13 +60,13 @@ Available as `google/gemma-4-{E2B,E4B,12B,26B-A4B,31B}-it-qat-q4_0-gguf`. ## Multimodal ```bash { .model-swap } -llama-mtmd-cli -hf ggml-org/gemma-4-E2B-it-GGUF +llama cli -hf ggml-org/gemma-4-E2B-it-GGUF --image "image.png" -p "Describe this image." ``` With local files, pass the projector explicitly: -```bash -llama-server -m gemma-4-12B-it-Q4_K_M.gguf --mmproj mmproj-gemma-4-12B-it.gguf +```bash { .model-swap } +llama cli -m gemma-4-E2B-it-Q4_0.gguf --mm mmproj-gemma-4-E2B-it-Q8_0.gguf ``` !!! note @@ -88,7 +76,7 @@ llama-server -m gemma-4-12B-it-Q4_K_M.gguf --mmproj mmproj-gemma-4-12B-it.gguf ## Server ```bash { .model-swap } -llama-server -hf ggml-org/gemma-4-E2B-it-GGUF +llama server -hf ggml-org/gemma-4-E2B-it-GGUF ``` Browser UI at `http://localhost:8080`, OpenAI-compatible API at `http://localhost:8080/v1`. diff --git a/docs/get-started/index.md b/docs/get-started/index.md index 5937f90..254c4ea 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -40,7 +40,7 @@ and move up without rewriting anything. pipe = pipeline( "text-generation", - model="google/gemma-4-12B-it", + model="google/gemma-4-E2B-it", device_map="auto" ) output = pipe("Explain quantum computing simply:", max_new_tokens=150) @@ -51,14 +51,14 @@ and move up without rewriting anything. Install [`llama.cpp`](https://llama.app/), then run: ```bash { .model-swap } - llama-cli -hf google/gemma-4-12B-it-qat-q4_0-gguf --prompt "Write a poem about the Kraken." + llama-cli -hf google/gemma-4-E2B-it-qat-q4_0-gguf --prompt "Write a poem about the Kraken." ``` === "Ollama" Install [`ollama`](https://ollama.com/download), then run: ```bash { .model-swap } - ollama run gemma4:12b "Explain quantum computing simply:" + ollama run gemma4:e2b "Explain quantum computing simply:" ``` === "Apple MLX" @@ -66,7 +66,7 @@ and move up without rewriting anything. Install [`MLX LM`](https://github.com/ml-explore/mlx-lm), then run: ```bash { .model-swap } - mlx_lm.generate --model mlx-community/gemma-4-12B-it-qat-4bit \ + mlx_lm.generate --model mlx-community/gemma-4-E2B-it-qat-4bit \ --prompt "Explain quantum computing simply:" ``` @@ -75,14 +75,14 @@ and move up without rewriting anything. Install [`vLLM`](https://docs.vllm.ai/en/latest/getting_started/installation/), then run: ```bash { .model-swap } - vllm serve google/gemma-4-26B-A4B-it + vllm serve google/gemma-4-E2B-it ``` === "LMStudio" Install [`LMStudio`](https://lmstudio.ai/), then run: ```bash { .model-swap } - lms chat google/gemma-4-12b + lms chat google/gemma-4-e2b ``` !!! example "Using a coding agent?" diff --git a/docs/javascripts/model-data.js b/docs/javascripts/model-data.js index 25f3798..8d69455 100644 --- a/docs/javascripts/model-data.js +++ b/docs/javascripts/model-data.js @@ -31,15 +31,18 @@ window.GEMMA_MODELS = { "hf": "google/gemma-4-E2B-it", "hf_base": "google/gemma-4-E2B", "hf_qat": "google/gemma-4-E2B-it-qat-w4a16-ct", - "gguf": "google/gemma-4-E2B-it-qat-q4_0-gguf", + "gguf": "ggml-org/gemma-4-E2B-it-GGUF", "gguf_qat": "google/gemma-4-E2B-it-qat-q4_0-gguf", + "gguf_file": "gemma-4-E2B-it-Q4_0.gguf", + "gguf_mmproj": "mmproj-gemma-4-E2B-it-Q8_0.gguf", "ollama": "gemma4:e2b", "mlx": "mlx-community/gemma-4-E2B-it-qat-4bit", "lmstudio": "google/gemma-4-e2b", "keras": "gemma4_instruct_2b", "keras_base": "gemma4_2b", "keras_assistant": "gemma4_instruct_2b_assistant", - "litert": "litert-community/gemma-4-E2B-it-litert-lm" + "litert": "litert-community/gemma-4-E2B-it-litert-lm", + "litert_file": "gemma-4-E2B-it.litertlm" }, "inputs": [ "text", @@ -57,15 +60,18 @@ window.GEMMA_MODELS = { "hf": "google/gemma-4-E4B-it", "hf_base": "google/gemma-4-E4B", "hf_qat": "google/gemma-4-E4B-it-qat-w4a16-ct", - "gguf": "google/gemma-4-E4B-it-qat-q4_0-gguf", + "gguf": "ggml-org/gemma-4-E4B-it-GGUF", "gguf_qat": "google/gemma-4-E4B-it-qat-q4_0-gguf", + "gguf_file": "gemma-4-E4B-it-Q4_0.gguf", + "gguf_mmproj": "mmproj-gemma-4-E4B-it-Q8_0.gguf", "ollama": "gemma4:e4b", "mlx": "mlx-community/gemma-4-E4B-it-qat-4bit", "lmstudio": "google/gemma-4-e4b", "keras": "gemma4_instruct_4b", "keras_base": "gemma4_4b", "keras_assistant": "gemma4_instruct_4b_assistant", - "litert": "litert-community/gemma-4-E4B-it-litert-lm" + "litert": "litert-community/gemma-4-E4B-it-litert-lm", + "litert_file": "gemma-4-E4B-it.litertlm" }, "inputs": [ "text", @@ -83,8 +89,10 @@ window.GEMMA_MODELS = { "hf": "google/gemma-4-12B-it", "hf_base": "google/gemma-4-12B", "hf_qat": "google/gemma-4-12B-it-qat-w4a16-ct", - "gguf": "google/gemma-4-12B-it-qat-q4_0-gguf", + "gguf": "ggml-org/gemma-4-12B-it-GGUF", "gguf_qat": "google/gemma-4-12B-it-qat-q4_0-gguf", + "gguf_file": "gemma-4-12B-it-Q4_0.gguf", + "gguf_mmproj": "mmproj-gemma-4-12B-it-Q8_0.gguf", "ollama": "gemma4:12b", "mlx": "mlx-community/gemma-4-12B-it-qat-4bit", "lmstudio": "google/gemma-4-12b" @@ -104,8 +112,10 @@ window.GEMMA_MODELS = { "ids": { "hf": "google/gemma-4-26B-A4B-it", "hf_base": "google/gemma-4-26B-A4B", - "gguf": "google/gemma-4-26B-A4B-it-qat-q4_0-gguf", + "gguf": "ggml-org/gemma-4-26B-A4B-it-GGUF", "gguf_qat": "google/gemma-4-26B-A4B-it-qat-q4_0-gguf", + "gguf_file": "gemma-4-26B-A4B-it-Q4_0.gguf", + "gguf_mmproj": "mmproj-gemma-4-26B-A4B-it-Q8_0.gguf", "ollama": "gemma4:26b", "mlx": "mlx-community/gemma-4-26B-A4B-it-qat-4bit", "lmstudio": "google/gemma-4-26b-a4b", @@ -129,8 +139,10 @@ window.GEMMA_MODELS = { "hf": "google/gemma-4-31B-it", "hf_base": "google/gemma-4-31B", "hf_qat": "google/gemma-4-31B-it-qat-w4a16-ct", - "gguf": "google/gemma-4-31B-it-qat-q4_0-gguf", + "gguf": "ggml-org/gemma-4-31B-it-GGUF", "gguf_qat": "google/gemma-4-31B-it-qat-q4_0-gguf", + "gguf_file": "gemma-4-31B-it-Q4_0.gguf", + "gguf_mmproj": "mmproj-gemma-4-31B-it-Q8_0.gguf", "ollama": "gemma4:31b", "mlx": "mlx-community/gemma-4-31B-it-qat-4bit", "lmstudio": "google/gemma-4-31b", diff --git a/docs/javascripts/model-switcher.js b/docs/javascripts/model-switcher.js index acc35ba..a80f09d 100644 --- a/docs/javascripts/model-switcher.js +++ b/docs/javascripts/model-switcher.js @@ -29,6 +29,11 @@ } } index.sort((a, b) => b.text.length - a.text.length); + const byText = new Map(index.map((entry) => [entry.text, entry])); + const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const pattern = index.length + ? new RegExp(index.map((entry) => escapeRegExp(entry.text)).join("|"), "g") + : null; const blocks = () => document.querySelectorAll(".model-swap code, code.model-swap"); const displayName = (id) => models[id]?.fullName || models[id]?.name || id; @@ -59,9 +64,11 @@ const enginesInUse = () => { const found = new Set(); + if (!pattern) return found; for (const block of blocks()) { - for (const entry of index) { - if (block.textContent.includes(entry.text)) found.add(entry.engine); + for (const [match] of block.textContent.matchAll(pattern)) { + const entry = byText.get(match); + if (entry) found.add(entry.engine); } } return found; @@ -78,21 +85,23 @@ }; const detect = () => { + if (!pattern) return null; for (const block of blocks()) { - const hit = index.find((entry) => block.textContent.includes(entry.text)); - if (hit) return hit.model; + for (const [match] of block.textContent.matchAll(pattern)) { + const entry = byText.get(match); + if (entry) return entry.model; + } } return null; }; const rewriteNode = (node, target) => { + if (!pattern) return; const original = node.__gemmaOriginal ?? node.nodeValue; - let next = original; - for (const entry of index) { - if (!next.includes(entry.text)) continue; - const replacement = models[target]?.ids?.[entry.engine]; - if (replacement) next = next.split(entry.text).join(replacement); - } + const next = original.replace(pattern, (match) => { + const entry = byText.get(match); + return (entry && models[target]?.ids?.[entry.engine]) || match; + }); if (next === node.nodeValue) return; node.__gemmaOriginal = original; node.nodeValue = next; @@ -281,7 +290,7 @@ : available[0]; buildControl(strip, available, initial, requested && !available.includes(requested) ? requested : null); - if (requested && available.includes(requested)) apply(requested); + apply(initial); }; let prevTabState = null; diff --git a/scripts/lint_docs.py b/scripts/lint_docs.py index 0cda6c3..4a4a410 100644 --- a/scripts/lint_docs.py +++ b/scripts/lint_docs.py @@ -565,9 +565,19 @@ def check_model_swap(ctx: Context) -> Iterable[Finding]: """A swappable engine must not also appear in unacknowledged static code blocks.""" data = yaml.safe_load((REPO_ROOT / "data" / "models.yml").read_text(encoding="utf-8")) engines: dict[str, set[str]] = {} + index: list[tuple[str, str, str]] = [] for size in data.get("gemma4", {}).get("sizes", []): + size_id = str(size["id"]) for engine, identifier in size.get("ids", {}).items(): engines.setdefault(str(engine), set()).add(str(identifier)) + index.append((str(identifier), str(engine), size_id)) + index.sort(key=lambda item: len(item[0]), reverse=True) + pattern = re.compile("|".join(re.escape(text) for text, _, _ in index)) + by_text = {text: (engine, size_id) for text, engine, size_id in index} + unrewritten_re = re.compile( + r"\bgemma[-_]?4[-_:(](?:e2b|e4b|12b|26b(?:[-_]a4b)?|31b)[\w.-]*", + re.IGNORECASE, + ) for page in ctx.pages: blocks = list(iter_code_blocks(page)) @@ -575,12 +585,40 @@ def check_model_swap(ctx: Context) -> Iterable[Finding]: if not marked: continue - active = { - engine - for _, _, code in marked - for engine, identifiers in engines.items() - if any(identifier in code for identifier in identifiers) - } + active: set[str] = set() + page_default_size: str | None = None + for line, _, code in marked: + matches = [m.group(0) for m in pattern.finditer(code)] + if not matches: + yield Finding( + "model-swap", + page.repo_rel, + line, + ".model-swap block contains no swappable model identifier from data/models.yml", + ) + continue + for match in matches: + engine, size_id = by_text[match] + active.add(engine) + if page_default_size is None: + page_default_size = size_id + elif size_id != page_default_size: + yield Finding( + "model-swap", + page.repo_rel, + line, + f".model-swap identifier '{match}' targets '{size_id}' " + f"but page default is '{page_default_size}'", + ) + remainder = pattern.sub("", code) + for leftover in unrewritten_re.finditer(remainder): + yield Finding( + "model-swap", + page.repo_rel, + line, + f"unrewritten model identifier '{leftover.group(0)}' inside .model-swap block", + ) + for line, info, code in blocks: if ".model-swap" in info or ".model-fixed" in info or ".output" in info: continue diff --git a/scripts/notebooks.py b/scripts/notebooks.py index bc88aa9..13b9431 100644 --- a/scripts/notebooks.py +++ b/scripts/notebooks.py @@ -196,13 +196,16 @@ def extract_swappable_blocks(body: str) -> list[str]: def detect_engines_in_use(body: str) -> set[str]: index = get_model_index() + if not index: + return set() + text_to_engine = {text: engine for text, engine, _ in index} + pattern = re.compile("|".join(re.escape(text) for text, _, _ in index)) blocks = extract_swappable_blocks(body) - engines = set() - for block in blocks: - for text, engine, _ in index: - if text in block: - engines.add(engine) - return engines + return { + text_to_engine[match.group(0)] + for block in blocks + for match in pattern.finditer(block) + } def candidate_models(source: Path) -> list[str]: From 32f0b9c3a62f3307c2436346041c8668199bb870 Mon Sep 17 00:00:00 2001 From: Ju-yeong Ji Date: Mon, 26 Oct 2026 10:28:16 +0900 Subject: [PATCH 04/22] Fix audio cell in non-audio notebooks --- docs/deployment/huggingface.md | 4 +- docs/javascripts/model-switcher.js | 47 +++++++++++++++++++ .../run-gemma-with-hf-gemma-4-26b-a4b.ipynb | 41 ---------------- .../run-gemma-with-hf-gemma-4-31b.ipynb | 41 ---------------- scripts/notebooks.py | 35 ++++++++++++++ tests/test_notebooks.py | 14 ++++++ 6 files changed, 99 insertions(+), 83 deletions(-) diff --git a/docs/deployment/huggingface.md b/docs/deployment/huggingface.md index 45c6cc7..9805c50 100644 --- a/docs/deployment/huggingface.md +++ b/docs/deployment/huggingface.md @@ -143,6 +143,7 @@ You can include multiple images in your prompt by including additional `"type": Do not use `<|image|>`{.token-multi}, ``{.token-multi} or ``{.token-multi} tokens in text portion of a prompt template as this approach creates redundant tokens and processing errors. + ## Generate text from audio data With [Gemma 4](../models/gemma-4/index.md) and [Gemma 3n](../models/gemma-3n/index.md), you can use audio data as part of your prompt. This section shows how to use the Transformers library to load and configure a Gemma model to use audio data and text input to generate text output. @@ -151,7 +152,7 @@ With [Gemma 4](../models/gemma-4/index.md) and [Gemma 3n](../models/gemma-3n/ind When generating content with audio, use a prompt template to structure your request. A prompt template allows you to specify input from specific roles, such as `user`{.token-role} or `model`{.token-role}, and is a required format for managing multi-turn chat interactions with Gemma models. The following example code shows how to constuct a prompt template for Gemma with audio data input: -```python { .notebook-cell } +```python { .notebook-cell data-requires="audio" } from transformers import GenerationConfig config = GenerationConfig.from_pretrained(MODEL_ID) config.max_new_tokens = 512 @@ -183,6 +184,7 @@ You can include multiple audio files in your prompt by including additional `"ty !!! Note Do not use `<|audio|>`{.token-multi} or ``{.token-multi} tokens in text portion of a prompt template as this approach creates redundant tokens and processing errors. + ## Next steps diff --git a/docs/javascripts/model-switcher.js b/docs/javascripts/model-switcher.js index a80f09d..f4def15 100644 --- a/docs/javascripts/model-switcher.js +++ b/docs/javascripts/model-switcher.js @@ -107,6 +107,52 @@ node.nodeValue = next; }; + const applyRequires = (target) => { + const inputs = models[target]?.inputs || []; + const article = document.querySelector(".md-content__inner"); + if (article) { + const walker = document.createTreeWalker(article, NodeFilter.SHOW_COMMENT); + const startRe = /^\s*notebook:requires-start\s+([a-z0-9_-]+)\s*$/i; + const endRe = /^\s*notebook:requires-end\s*$/i; + const starts = []; + let comment; + while ((comment = walker.nextNode())) { + const match = comment.nodeValue.match(startRe); + if (match) starts.push({ node: comment, modality: match[1].toLowerCase() }); + } + for (const { node, modality } of starts) { + const supported = inputs.includes(modality); + let cur = node.nextSibling; + while (cur) { + if (cur.nodeType === Node.COMMENT_NODE && endRe.test(cur.nodeValue)) break; + if (cur.nodeType === Node.ELEMENT_NODE) { + cur.hidden = !supported; + cur.style.display = supported ? "" : "none"; + if (cur.id && /^H[1-6]$/.test(cur.tagName)) { + const targetHash = `#${cur.id}`; + for (const link of document.querySelectorAll(".md-nav--secondary a.md-nav__link")) { + if (link.hash !== targetHash) continue; + const item = link.closest(".md-nav__item"); + if (item) { + item.hidden = !supported; + item.style.display = supported ? "" : "none"; + } + } + } + } + cur = cur.nextSibling; + } + } + } + for (const el of document.querySelectorAll("[data-requires]:not(.page-context)")) { + const modality = el.dataset.requires?.trim().toLowerCase(); + if (!modality) continue; + const supported = inputs.includes(modality); + el.hidden = !supported; + el.style.display = supported ? "" : "none"; + } + }; + const apply = (target) => { if (!models[target]) return; for (const block of blocks()) { @@ -114,6 +160,7 @@ let node; while ((node = walker.nextNode())) rewriteNode(node, target); } + applyRequires(target); }; const positionMenu = (picker) => { diff --git a/docs/notebooks/run-gemma-with-hf-gemma-4-26b-a4b.ipynb b/docs/notebooks/run-gemma-with-hf-gemma-4-26b-a4b.ipynb index 6c9aa38..9d365a9 100644 --- a/docs/notebooks/run-gemma-with-hf-gemma-4-26b-a4b.ipynb +++ b/docs/notebooks/run-gemma-with-hf-gemma-4-26b-a4b.ipynb @@ -203,47 +203,6 @@ "You can include multiple images in your prompt by including additional `\"type\": \"image\",` entries in the `content` list.\n", "> **Note:** Do not use `<|image|>`, `` or `` tokens in text portion of a prompt template as this approach creates redundant tokens and processing errors.\n", "\n", - "## Generate text from audio data\n", - "\n", - "With [Gemma 4](../models/gemma-4/index.md) and [Gemma 3n](../models/gemma-3n/index.md), you can use audio data as part of your prompt. This section shows how to use the Transformers library to load and configure a Gemma model to use audio data and text input to generate text output.\n", - "\n", - "### Use a prompt template\n", - "\n", - "When generating content with audio, use a prompt template to structure your request. A prompt template allows you to specify input from specific roles, such as `user` or `model`, and is a required format for managing multi-turn chat interactions with Gemma models. The following example code shows how to constuct a prompt template for Gemma with audio data input:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "from transformers import GenerationConfig\n", - "config = GenerationConfig.from_pretrained(MODEL_ID)\n", - "config.max_new_tokens = 512\n", - "gen_kwargs = dict(generation_config=config)\n", - "\n", - "messages = [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": [\n", - " {\"type\": \"text\", \"text\": \"Transcribe the following speech segment in its original language. Follow these specific instructions for formatting the answer:\\n* Only output the transcription, with no newlines.\\n* When transcribing numbers, write the digits, i.e. write 1.7 and not one point seven, and write 3 instead of three.\"},\n", - " {\"type\": \"audio\", \"audio\": \"https://ai.google.dev/gemma/docs/audio/roses-are.wav\"},\n", - " ]\n", - " }\n", - "]\n", - "\n", - "pipe(text=messages, return_full_text=False, generate_kwargs=gen_kwargs)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can include multiple audio files in your prompt by including additional `\"type\": \"audio\",` entries in the `content` list.\n", - "> **Note:** Do not use `<|audio|>` or `` tokens in text portion of a prompt template as this approach creates redundant tokens and processing errors.\n", "\n", "## Next steps\n", "\n", diff --git a/docs/notebooks/run-gemma-with-hf-gemma-4-31b.ipynb b/docs/notebooks/run-gemma-with-hf-gemma-4-31b.ipynb index 625ef6f..897e7aa 100644 --- a/docs/notebooks/run-gemma-with-hf-gemma-4-31b.ipynb +++ b/docs/notebooks/run-gemma-with-hf-gemma-4-31b.ipynb @@ -203,47 +203,6 @@ "You can include multiple images in your prompt by including additional `\"type\": \"image\",` entries in the `content` list.\n", "> **Note:** Do not use `<|image|>`, `` or `` tokens in text portion of a prompt template as this approach creates redundant tokens and processing errors.\n", "\n", - "## Generate text from audio data\n", - "\n", - "With [Gemma 4](../models/gemma-4/index.md) and [Gemma 3n](../models/gemma-3n/index.md), you can use audio data as part of your prompt. This section shows how to use the Transformers library to load and configure a Gemma model to use audio data and text input to generate text output.\n", - "\n", - "### Use a prompt template\n", - "\n", - "When generating content with audio, use a prompt template to structure your request. A prompt template allows you to specify input from specific roles, such as `user` or `model`, and is a required format for managing multi-turn chat interactions with Gemma models. The following example code shows how to constuct a prompt template for Gemma with audio data input:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "from transformers import GenerationConfig\n", - "config = GenerationConfig.from_pretrained(MODEL_ID)\n", - "config.max_new_tokens = 512\n", - "gen_kwargs = dict(generation_config=config)\n", - "\n", - "messages = [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": [\n", - " {\"type\": \"text\", \"text\": \"Transcribe the following speech segment in its original language. Follow these specific instructions for formatting the answer:\\n* Only output the transcription, with no newlines.\\n* When transcribing numbers, write the digits, i.e. write 1.7 and not one point seven, and write 3 instead of three.\"},\n", - " {\"type\": \"audio\", \"audio\": \"https://ai.google.dev/gemma/docs/audio/roses-are.wav\"},\n", - " ]\n", - " }\n", - "]\n", - "\n", - "pipe(text=messages, return_full_text=False, generate_kwargs=gen_kwargs)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can include multiple audio files in your prompt by including additional `\"type\": \"audio\",` entries in the `content` list.\n", - "> **Note:** Do not use `<|audio|>` or `` tokens in text portion of a prompt template as this approach creates redundant tokens and processing errors.\n", "\n", "## Next steps\n", "\n", diff --git a/scripts/notebooks.py b/scripts/notebooks.py index 13b9431..bdcfba7 100644 --- a/scripts/notebooks.py +++ b/scripts/notebooks.py @@ -73,6 +73,16 @@ r".*?", re.DOTALL, ) +REQUIRES_REGION = re.compile( + r"[ \t]*\n?" + r"(?P.*?)" + r"[ \t]*\n?", + re.DOTALL, +) +REQUIRES_COMMENT = re.compile( + r"[ \t]*\n?" +) +FENCE_REQUIRES = re.compile(r"\b(?:data-)?requires=[\"'](?P[a-z0-9_-]+)[\"']") ADMONITION = re.compile( r"^!!!\s+(?P