Views
No views yet
poolside/Laguna-XS.2. It does not include the Laguna base weights or tokenizer.duo_attention/full_attention_heads.pt, and enables DuoAttention
with sink size 64 and recent size 256.trust_remote_code=True loading applies the Laguna DuoAttention patch.





poolside/Laguna-XS.2. The base line accounts for dense Laguna FP8 KV cache;
the DuoAttention path stores retrieval heads as FP8 and streaming heads as
packed INT4 with per-group scale/zero-point metadata.| Prompt | Decode | Base KV | Duo KV | KV Reduction |
|---|---|---|---|---|
| 512 | 1 | 40.08 MiB | 24.03 MiB | 40.04% |
| 512 | 16 | 41.25 MiB | 24.50 MiB | 40.61% |
| 512 | 64 | 45.00 MiB | 26.00 MiB | 42.22% |
| 1,024 | 1 | 80.08 MiB | 40.03 MiB | 50.01% |
| 1,024 | 16 | 81.25 MiB | 40.50 MiB | 50.15% |
| 1,024 | 64 | 85.00 MiB | 42.00 MiB | 50.59% |
| 1,462 | 1 | 114.30 MiB | 53.72 MiB | 53.00% |
| 1,462 | 16 | 115.47 MiB | 54.19 MiB | 53.07% |
| 1,462 | 64 | 119.22 MiB | 55.69 MiB | 53.29% |

g_proj gated output path when splitting full-context and
streaming heads.full_attention_heads tensor at load time.pip install sentencepiece tiktoken1import gc
2import torch
3from transformers import AutoModelForCausalLM, AutoTokenizer
4
5adapter_repo = "dogeplusplus/duo-laguna-adapter"
6base_model_id = "poolside/Laguna-XS.2"
7
8tokenizer = AutoTokenizer.from_pretrained(
9 base_model_id,
10 trust_remote_code=True,
11 token=True,
12)
13model_kwargs = {
14 "trust_remote_code": True,
15 "token": True,
16}
17if torch.cuda.is_available():
18 model_kwargs["dtype"] = torch.bfloat16
19 model_kwargs["device_map"] = {"": "cuda:0"}
20else:
21 model_kwargs["torch_dtype"] = "auto"
22 model_kwargs["device_map"] = "auto"
23
24
25def cache_nbytes(value):
26 if value is None:
27 return 0
28 if torch.is_tensor(value):
29 return value.numel() * value.element_size()
30 if hasattr(value, "key_cache") and hasattr(value, "value_cache"):
31 return cache_nbytes(value.key_cache) + cache_nbytes(value.value_cache)
32 if hasattr(value, "to_legacy_cache"):
33 try:
34 return cache_nbytes(value.to_legacy_cache())
35 except Exception:
36 pass
37 if isinstance(value, dict):
38 return sum(cache_nbytes(v) for v in value.values())
39 if isinstance(value, (list, tuple)):
40 return sum(cache_nbytes(v) for v in value)
41 return 0
42
43
44def first_parameter_device(model):
45 return next(model.parameters()).device
46
47
48def dense_kv_cache_nbytes(config, tokens, dtype):
49 num_layers = config.num_hidden_layers
50 num_key_value_heads = config.num_key_value_heads
51 head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
52 bytes_per_value = torch.empty((), dtype=dtype).element_size()
53 return num_layers * 2 * num_key_value_heads * tokens * head_dim * bytes_per_value
54
55
56def clear_cuda():
57 gc.collect()
58 if torch.cuda.is_available():
59 torch.cuda.empty_cache()
60 torch.cuda.ipc_collect()
61
62
63def greedy_decode_from_prefill(model, prefill, input_ids, max_new_tokens):
64 past_key_values = prefill.past_key_values
65 next_token = prefill.logits[:, -1, :].argmax(dim=-1, keepdim=True)
66 generated = [input_ids, next_token]
67 for _ in range(max_new_tokens - 1):
68 out = model(
69 input_ids=next_token,
70 past_key_values=past_key_values,
71 use_cache=True,
72 )
73 past_key_values = out.past_key_values
74 next_token = out.logits[:, -1, :].argmax(dim=-1, keepdim=True)
75 generated.append(next_token)
76 return torch.cat(generated, dim=-1)
77
78
79prompt = (
80 "Remember this retrieval key: RIVER-4821. "
81 + "The notebook contains many irrelevant meeting notes. " * 180
82 + "Question: what is the retrieval key?"
83)
84
85base_model = AutoModelForCausalLM.from_pretrained(
86 base_model_id,
87 **model_kwargs,
88).eval()
89inputs = tokenizer(prompt, return_tensors="pt").to(first_parameter_device(base_model))
90with torch.no_grad():
91 base_out = base_model(**inputs, use_cache=True)
92base_cache_bytes = cache_nbytes(base_out.past_key_values)
93if base_cache_bytes == 0:
94 base_cache_bytes = dense_kv_cache_nbytes(
95 base_model.config,
96 inputs["input_ids"].shape[-1],
97 next(base_model.parameters()).dtype,
98 )
99base_cache_mib = base_cache_bytes / 2**20
100del base_out, inputs, base_model
101clear_cuda()
102
103duo_model = AutoModelForCausalLM.from_pretrained(
104 adapter_repo,
105 **model_kwargs,
106).eval()
107duo_inputs = tokenizer(prompt, return_tensors="pt").to(first_parameter_device(duo_model))
108with torch.no_grad():
109 duo_out = duo_model(**duo_inputs, use_cache=True)
110 generated = greedy_decode_from_prefill(duo_model, duo_out, duo_inputs["input_ids"], 64)
111duo_cache_mib = cache_nbytes(duo_out.past_key_values) / 2**20
112
113print(f"Base KV cache: {base_cache_mib:.2f} MiB")
114print(f"Duo KV cache: {cache_nbytes(duo_out.past_key_values) / 2**20:.2f} MiB")
115print(f"KV reduction: {100 * (1 - duo_cache_mib / base_cache_mib):.1f}%")
116
117print(tokenizer.decode(generated[0], skip_special_tokens=True))token=True after hf auth login, or pass a token string directly for
private or gated repositories.