bmap-gemma-engine / gemma /diffusion /_chat_sampler.py
Nanny7's picture
feat: initial commit for B Map AI backend and Gemma LLM service
2f86d9b
Raw History Blame Contribute Delete
7.07 kB
# Copyright 2026 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Diffusion-specific sampler and chat sampler."""
import dataclasses
import functools
from typing import override
from gemma import gm
from gemma.diffusion import _early_stopping
from gemma.diffusion import _sampler
from gemma.gm.text import _sampler_loop
@dataclasses.dataclass(frozen=True, kw_only=True)
class Sampler(gm.text.Sampler):
"""Diffusion variant of `gm.text.Sampler`.
This class overrides `_initialize_sampler_loop` to create a
`DiffusionSampler`, which extends `SamplerLoop` with block-wise diffusion
sampling.
Attributes:
diffusion_process: Diffusion process to use. When unset, use the default
preset.
logit_shaper: Temperature annealing shaper. When unset, use the default
preset.
sample_from_predictions: Sampling strategy for denoised predictions. When
unset, use the default preset.
canvas_length: Diffusion canvas length to use. If unset, the model default
preset is used.
max_denoising_steps: Maximum number of denoising steps per completed canvas.
If unset, the model default preset is used.
"""
diffusion_process: _sampler.DiffusionProcess = dataclasses.field(
default_factory=_sampler.DiffusionProcess
)
logit_shaper: _sampler.AnnealingTemperatureShaper = dataclasses.field(
default_factory=lambda: _sampler.AnnealingTemperatureShaper(
config=_sampler.AnnealingTemperatureShaperConfig()
)
)
sample_from_predictions: _sampler.SampleFromPredictions = dataclasses.field(
default_factory=lambda: _sampler.SampleFromPredictions(
entropy_bound=0.1,
)
)
early_stop_fn: _early_stopping.EarlyStopFn = dataclasses.field(
default_factory=lambda: _early_stopping.ChainedEarlyStop(
early_stop_fns=(
_early_stopping.TokenStabilityEarlyStop(),
_early_stopping.EntropyEarlyStop(entropy_threshold=0.005),
),
)
)
canvas_length: int = 256
max_denoising_steps: int = 48
@override
def _initialize_sampler_loop(self, sampling) -> _sampler_loop.SamplerLoop:
"""Initializes the sampler loop."""
# Ensure SampleFromPredictions gets the vocab size.
sample_from_predictions = self.sample_from_predictions
if sample_from_predictions.text_vocab_size == 0:
sample_from_predictions = dataclasses.replace(
sample_from_predictions,
text_vocab_size=self.tokenizer.vocab_size,
)
return _sampler.DiffusionSampler(
model=self.model,
end_tokens=(
self.tokenizer.special_tokens.EOS,
self.tokenizer.special_tokens.END_OF_TURN,
self.tokenizer.special_tokens.BEGIN_OF_TOOL_RESPONSE,
*self._normalized_stop_tokens,
),
forbidden_tokens=self._normalized_forbidden_tokens,
sampling=sampling,
cache_length=self.cache_length,
special_tokens=self.tokenizer.special_tokens,
diffusion_process=self.diffusion_process,
logit_shaper=self.logit_shaper,
sample_from_predictions=sample_from_predictions,
canvas_length=self.canvas_length,
max_denoising_steps=self.max_denoising_steps,
text_vocab_size=self.tokenizer.vocab_size,
sliding_window_size=getattr(
self.model.config, 'sliding_window_size', None
),
early_stop_fn=self.early_stop_fn,
)
@dataclasses.dataclass(frozen=True, kw_only=True, eq=False)
class ChatSampler(gm.text.ChatSampler):
"""Diffusion equivalent of `gm.text.ChatSampler`.
Check the docstring of `gm.text.ChatSampler` for usage. The only differences
are diffusion-specific arguments in the constructor.
Attributes:
diffusion_process: Diffusion process to use. When unset, use the default
preset.
logit_shaper: Temperature annealing shaper. When unset, use the default
preset.
sample_from_predictions: Sampling strategy for denoised predictions. When
unset, use the default preset.
canvas_length: Diffusion canvas length to use. If unset, the model default
preset is used.
max_denoising_steps: Maximum number of denoising steps per completed canvas.
If unset, the model default preset is used.
"""
diffusion_process: _sampler.DiffusionProcess = dataclasses.field(
default_factory=_sampler.DiffusionProcess
)
logit_shaper: _sampler.AnnealingTemperatureShaper = dataclasses.field(
default_factory=lambda: _sampler.AnnealingTemperatureShaper(
config=_sampler.AnnealingTemperatureShaperConfig()
)
)
sample_from_predictions: _sampler.SampleFromPredictions = dataclasses.field(
default_factory=lambda: _sampler.SampleFromPredictions(
entropy_bound=0.1,
)
)
early_stop_fn: _early_stopping.EarlyStopFn = dataclasses.field(
default_factory=lambda: _early_stopping.ChainedEarlyStop(
early_stop_fns=(
_early_stopping.TokenStabilityEarlyStop(),
_early_stopping.EntropyEarlyStop(entropy_threshold=0.005),
),
)
)
canvas_length: int = 256
max_denoising_steps: int = 48
@override
@functools.cached_property
def sampler(self) -> Sampler:
"""Returns the underlying sampler."""
return Sampler(
model=self.model,
params=self.params,
tokenizer=self.tokenizer,
sampling=self.sampling,
forbidden_tokens=self.forbidden_tokens,
stop_tokens=self.stop_tokens,
cache_length=self.cache_length, # pyrefly: ignore[bad-argument-type]
max_out_length=self.max_out_length,
pad_length=self.pad_length,
diffusion_process=self.diffusion_process,
logit_shaper=self.logit_shaper,
sample_from_predictions=self.sample_from_predictions,
canvas_length=self.canvas_length,
max_denoising_steps=self.max_denoising_steps,
early_stop_fn=self.early_stop_fn,
)
@override
def _sample(
self,
prompt_text,
*,
images,
audio,
audio_lengths,
sampling,
max_new_tokens,
rng,
last_state,
stream,
sharding
):
"""Override to always use the diffusion sampler."""
return self.sampler.sample( # pyrefly: ignore[no-matching-overload]
prompt_text,
images=images,
sampling=sampling,
max_new_tokens=max_new_tokens,
rng=rng,
return_state=True,
last_state=last_state,
stream=bool(stream),
sharding=sharding,
)