DeanoCalver's picture
Initial DotCache Arena Space upload
751ad26 verified
Raw
History Blame Contribute Delete
12.3 kB
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import numpy as np
from ..config import DotCacheConfig
from .llama import (
LlamaDotCacheHarness,
LlamaDotCacheModelAdapter,
LlamaReplayRecord,
_default_model_device,
_require_transformers,
_timed_call,
_torch_backend_matches_device,
resolve_hf_auth_kwargs,
run_llama_generation_harness,
run_llama_loss_harness,
run_llama_replay_harness,
transformers_available,
)
if transformers_available():
import torch
import torch.nn as nn
import transformers.models.qwen2.modeling_qwen2 as qwen2_mod
from transformers import AutoModelForCausalLM, AutoTokenizer
else: # pragma: no cover - exercised in environments without transformers
torch = None
nn = object # type: ignore[assignment]
qwen2_mod = None
AutoModelForCausalLM = None
AutoTokenizer = None
class DotCacheQwen2Attention(nn.Module):
def __init__(self, base_attention: nn.Module, adapter: "Qwen2DotCacheModelAdapter") -> None:
super().__init__()
self.base_attention = base_attention
self.adapter = adapter
self.layer_idx = int(base_attention.layer_idx)
self.config = base_attention.config
def forward(
self,
hidden_states: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
attention_mask: torch.Tensor | None = None,
past_key_values=None,
cache_position: torch.LongTensor | None = None,
**kwargs: Any,
) -> tuple[torch.Tensor, torch.Tensor | None]:
if self.adapter.mode == "dense" and not self.adapter.capture_enabled:
return self.base_attention(
hidden_states,
position_embeddings=position_embeddings,
attention_mask=attention_mask,
past_key_values=past_key_values,
cache_position=cache_position,
**kwargs,
)
if self.adapter.mode == "dense":
return self._forward_dense_with_capture(
hidden_states,
position_embeddings=position_embeddings,
attention_mask=attention_mask,
past_key_values=past_key_values,
cache_position=cache_position,
**kwargs,
)
return self._forward_dotcache(
hidden_states,
position_embeddings=position_embeddings,
attention_mask=attention_mask,
past_key_values=past_key_values,
cache_position=cache_position,
**kwargs,
)
def _project_qkv(
self,
hidden_states: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
if position_embeddings is None:
raise ValueError("position_embeddings are required for the Qwen2 attention path")
input_shape = hidden_states.shape[:-1]
hidden_shape = (*input_shape, -1, self.base_attention.head_dim)
query_states = self.base_attention.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
key_states = self.base_attention.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
value_states = self.base_attention.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
cos, sin = position_embeddings
query_states, key_states = qwen2_mod.apply_rotary_pos_emb(query_states, key_states, cos, sin)
return query_states, key_states, value_states
def _forward_dense_with_capture(
self,
hidden_states: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
attention_mask: torch.Tensor | None = None,
past_key_values=None,
cache_position: torch.LongTensor | None = None,
**kwargs: Any,
) -> tuple[torch.Tensor, torch.Tensor | None]:
input_shape = hidden_states.shape[:-1]
query_states, key_states, value_states = self._project_qkv(hidden_states, position_embeddings)
fresh_key_states = key_states
fresh_value_states = value_states
if past_key_values is not None:
cos, sin = position_embeddings
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
attention_interface = qwen2_mod.ALL_ATTENTION_FUNCTIONS.get_interface(
self.base_attention.config._attn_implementation,
qwen2_mod.eager_attention_forward,
)
attn_output, attn_weights = attention_interface(
self.base_attention,
query_states,
key_states,
value_states,
attention_mask,
dropout=0.0 if not self.training else self.base_attention.attention_dropout,
scaling=self.base_attention.scaling,
sliding_window=self.base_attention.sliding_window,
**kwargs,
)
reshaped_output = attn_output.reshape(*input_shape, -1).contiguous()
projected_output = self.base_attention.o_proj(reshaped_output)
if self.adapter.capture_enabled and tuple(hidden_states.shape[:2]) == (1, 1):
token_index = self.adapter.current_token_index(cache_position)
self.adapter.record_replay(
LlamaReplayRecord(
step_index=self.adapter.capture_step_index,
layer_id=self.layer_idx,
token_index=token_index,
query_states=query_states[0, :, 0, :].detach().to(dtype=torch.float32).cpu().numpy(),
key_states=fresh_key_states[0, :, 0, :].detach().to(dtype=torch.float32).cpu().numpy(),
value_states=fresh_value_states[0, :, 0, :].detach().to(dtype=torch.float32).cpu().numpy(),
context_states=attn_output[0, 0].detach().to(dtype=torch.float32).cpu().numpy(),
output_states=projected_output[0, 0].detach().to(dtype=torch.float32).cpu().numpy(),
)
)
return projected_output, attn_weights
def _forward_dotcache(
self,
hidden_states: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
attention_mask: torch.Tensor | None = None,
past_key_values=None,
cache_position: torch.LongTensor | None = None,
**kwargs: Any,
) -> tuple[torch.Tensor, None]:
del attention_mask, kwargs
if past_key_values is not None:
raise ValueError("DotCache decode mode manages its own KV cache and requires past_key_values=None")
if tuple(hidden_states.shape[:2]) != (1, 1):
raise ValueError("DotCache decode mode only supports batch=1 and query_len=1")
token_index = self.adapter.current_token_index(cache_position)
(query_states, key_states, value_states), qkv_ms = _timed_call(
lambda: self._project_qkv(hidden_states, position_embeddings),
device=hidden_states.device,
)
query_step = query_states[0, :, 0, :].detach().to(dtype=torch.float32)
key_step = key_states[0].detach().to(dtype=torch.float32)
value_step = value_states[0].detach().to(dtype=torch.float32)
self.adapter.record_layer_runtime(self.layer_idx, qkv_projection_ms=qkv_ms)
_, append_ms = _timed_call(
lambda: self.adapter.model_kv_cache.append_step_torch(
self.layer_idx,
key_step,
value_step,
token_index,
trace=self.adapter.active_trace,
)
if _torch_backend_matches_device(self.adapter.backend, hidden_states.device.type)
else self.adapter.model_kv_cache.append_step(
self.layer_idx,
key_step.cpu().numpy(),
value_step.cpu().numpy(),
token_index,
trace=self.adapter.active_trace,
),
device=hidden_states.device,
)
self.adapter.append_runtime_ms_total += append_ms
context_states, decode_ms = _timed_call(
lambda: self.adapter.model_kv_cache.decode_layer_torch(
self.layer_idx,
query_step,
self.adapter.q_head_to_kv_head,
query_scale=float(self.base_attention.scaling),
trace=self.adapter.active_trace,
)
if _torch_backend_matches_device(self.adapter.backend, hidden_states.device.type)
else self.adapter.model_kv_cache.decode_layer(
self.layer_idx,
query_step.detach().cpu().numpy(),
self.adapter.q_head_to_kv_head,
query_scale=float(self.base_attention.scaling),
trace=self.adapter.active_trace,
),
device=hidden_states.device,
)
self.adapter.decode_runtime_ms_total += decode_ms
def _project_output():
local_context_states = context_states
if not torch.is_tensor(local_context_states):
local_context_states = torch.as_tensor(local_context_states, dtype=torch.float32, device=hidden_states.device)
context_tensor = local_context_states.to(dtype=hidden_states.dtype, device=hidden_states.device).unsqueeze(0)
return self.base_attention.o_proj(context_tensor.reshape(1, 1, -1).contiguous())
projected_output, output_projection_ms = _timed_call(_project_output, device=hidden_states.device)
self.adapter.record_layer_runtime(
self.layer_idx,
append_ms=append_ms,
decode_ms=decode_ms,
output_projection_ms=output_projection_ms,
)
if self.adapter.capture_enabled:
self.adapter.record_replay(
LlamaReplayRecord(
step_index=self.adapter.capture_step_index,
layer_id=self.layer_idx,
token_index=token_index,
query_states=query_step.detach().cpu().numpy(),
key_states=key_step[:, 0, :].detach().cpu().numpy(),
value_states=value_step[:, 0, :].detach().cpu().numpy(),
context_states=context_states.detach().cpu().numpy().astype(np.float32, copy=False),
output_states=projected_output[0, 0].detach().to(dtype=torch.float32).cpu().numpy(),
)
)
return projected_output, None
class Qwen2DotCacheModelAdapter(LlamaDotCacheModelAdapter):
def _install_wrappers(self) -> None:
for layer in self.model.model.layers[: self.model.config.num_hidden_layers]:
wrapper = DotCacheQwen2Attention(layer.self_attn, self)
layer.self_attn = wrapper
self._wrappers.append(wrapper)
@dataclass(slots=True)
class Qwen2DotCacheHarness(LlamaDotCacheHarness):
adapter: Qwen2DotCacheModelAdapter
@classmethod
def from_pretrained(
cls,
model_id: str,
dotcache_config: DotCacheConfig,
*,
backend: str = "auto",
device: str | None = None,
torch_dtype: str = "float16",
) -> "Qwen2DotCacheHarness":
_require_transformers()
dtype = getattr(torch, torch_dtype)
resolved_device = _default_model_device() if device is None else device
auth_kwargs = resolve_hf_auth_kwargs()
model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=dtype, **auth_kwargs)
model.to(resolved_device)
model.eval()
tokenizer = AutoTokenizer.from_pretrained(model_id, **auth_kwargs)
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id
adapter = Qwen2DotCacheModelAdapter(model, dotcache_config, backend=backend)
return cls(model=model, tokenizer=tokenizer, adapter=adapter)
run_qwen2_replay_harness = run_llama_replay_harness
run_qwen2_generation_harness = run_llama_generation_harness
run_qwen2_loss_harness = run_llama_loss_harness