Image-Text-to-Text
Transformers
Safetensors
English
locateanything
feature-extraction
nvidia
eagle
vision
object-detection
grounding
conversational
custom_code
Instructions to use nvidia/LocateAnything-3B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use nvidia/LocateAnything-3B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="nvidia/LocateAnything-3B", trust_remote_code=True) messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("nvidia/LocateAnything-3B", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use nvidia/LocateAnything-3B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "nvidia/LocateAnything-3B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "nvidia/LocateAnything-3B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/nvidia/LocateAnything-3B
- SGLang
How to use nvidia/LocateAnything-3B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "nvidia/LocateAnything-3B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "nvidia/LocateAnything-3B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "nvidia/LocateAnything-3B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "nvidia/LocateAnything-3B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use nvidia/LocateAnything-3B with Docker Model Runner:
docker model run hf.co/nvidia/LocateAnything-3B
Add batch inference and LA Flash runtime
Browse files- README.md +61 -2
- batch_infer.py +129 -0
- batch_utils/README.md +53 -0
- batch_utils/__init__.py +12 -0
- batch_utils/engine_hybrid.py +1357 -0
- batch_utils/hybrid_runtime.py +1842 -0
- kernel_utils/README.md +53 -0
- kernel_utils/__init__.py +5 -0
- kernel_utils/range_attention.py +394 -0
- pyproject.toml +18 -0
README.md
CHANGED
|
@@ -31,7 +31,8 @@ base_model:
|
|
| 31 |
* 🚀 **Online Demo**: [LocateAnything (Hugging Face Spaces)](https://huggingface.co/spaces/nvidia/LocateAnything)
|
| 32 |
* 💻 **GitHub Code**: [NVlabs/Eagle/Embodied](https://github.com/NVlabs/Eagle/tree/main/Embodied)
|
| 33 |
* 📄 **Paper**: [arXiv:2605.27365](https://arxiv.org/abs/2605.27365)
|
| 34 |
-
|
|
|
|
| 35 |
# Model Overview
|
| 36 |
|
| 37 |
### Description:
|
|
@@ -263,6 +264,37 @@ Test Hardware: H100
|
|
| 263 |
|
| 264 |
We suggest using `max_new_tokens=8192` and `generation_mode="hybrid"` to avoid truncated response and balance speed with accuracy.
|
| 265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
### Installation
|
| 267 |
|
| 268 |
```bash
|
|
@@ -489,9 +521,36 @@ points = LocateAnythingWorker.parse_points(result["answer"], w, h)
|
|
| 489 |
| `slow` | Pure auto-regressive decoding | Slowest | Most robust |
|
| 490 |
| `hybrid` (default) | MTP first, falls back to AR on uncertain boxes, switches back after box boundary | Balanced | Best overall |
|
| 491 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
## Ethical Considerations:
|
| 493 |
NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal model team to ensure this model meets requirements for the relevant industry and use case and addresses unforeseen product misuse.
|
| 494 |
|
| 495 |
Please make sure you have proper rights and permissions for all input image and video content; if image or video includes people, personal health information, or intellectual property, the image or video generated will not blur or maintain proportions of image subjects included.
|
| 496 |
|
| 497 |
-
Please report model quality, risk, security vulnerabilities or NVIDIA AI Concerns [here](https://app.intigriti.com/programs/nvidia/nvidiavdp/detail).
|
|
|
|
| 31 |
* 🚀 **Online Demo**: [LocateAnything (Hugging Face Spaces)](https://huggingface.co/spaces/nvidia/LocateAnything)
|
| 32 |
* 💻 **GitHub Code**: [NVlabs/Eagle/Embodied](https://github.com/NVlabs/Eagle/tree/main/Embodied)
|
| 33 |
* 📄 **Paper**: [arXiv:2605.27365](https://arxiv.org/abs/2605.27365)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
# Model Overview
|
| 37 |
|
| 38 |
### Description:
|
|
|
|
| 264 |
|
| 265 |
We suggest using `max_new_tokens=8192` and `generation_mode="hybrid"` to avoid truncated response and balance speed with accuracy.
|
| 266 |
|
| 267 |
+
### Batch Hybrid Inference
|
| 268 |
+
|
| 269 |
+
This release includes `batch_infer.py`, `batch_utils`, and `kernel_utils` for
|
| 270 |
+
high-throughput detection and grounding. The `la_flash` backend is a pure
|
| 271 |
+
FlashAttention-varlen sparse range executor: it keeps LocateAnything's hybrid
|
| 272 |
+
MTP decoding path, avoids dense `[B,H,Q,K]` SDPA masks, and does not require a
|
| 273 |
+
custom CUDA extension build.
|
| 274 |
+
|
| 275 |
+
Use it with:
|
| 276 |
+
|
| 277 |
+
```bash
|
| 278 |
+
python batch_infer.py \
|
| 279 |
+
--model . \
|
| 280 |
+
--attn la_flash \
|
| 281 |
+
--scheduler pipeline \
|
| 282 |
+
--batch-size 4 \
|
| 283 |
+
--image /path/to/image.jpg \
|
| 284 |
+
--query "person</c>car"
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
A100 4K probe, real 3840x2160 street image, `query=vehicle`,
|
| 288 |
+
`batch_size=4`, raw PIL input, `in_token_limit=25600`, hybrid MTP inference:
|
| 289 |
+
|
| 290 |
+
| Backend | Attention Path | Time | Peak Reserved Memory |
|
| 291 |
+
| --- | --- | ---: | ---: |
|
| 292 |
+
| `sdpa` | Dense SDPA masks | 8.2600 s | 35.12 GB |
|
| 293 |
+
| `la_flash` | FlashAttention sparse range plan | 8.0314 s | 11.71 GB |
|
| 294 |
+
|
| 295 |
+
See `batch_utils/README.md` and `kernel_utils/README.md` for runtime knobs and
|
| 296 |
+
implementation details.
|
| 297 |
+
|
| 298 |
### Installation
|
| 299 |
|
| 300 |
```bash
|
|
|
|
| 521 |
| `slow` | Pure auto-regressive decoding | Slowest | Most robust |
|
| 522 |
| `hybrid` (default) | MTP first, falls back to AR on uncertain boxes, switches back after box boundary | Balanced | Best overall |
|
| 523 |
|
| 524 |
+
## Batch Utils and Kernel Utils
|
| 525 |
+
|
| 526 |
+
This repository also includes optional utilities for high-throughput detection
|
| 527 |
+
runs:
|
| 528 |
+
|
| 529 |
+
- `batch_infer.py`: JSONL/image-query batch inference CLI.
|
| 530 |
+
- `batch_utils/`: batched hybrid generation runtime. See
|
| 531 |
+
`batch_utils/README.md`.
|
| 532 |
+
- `kernel_utils/`: LA Flash sparse range utilities. See
|
| 533 |
+
`kernel_utils/README.md`.
|
| 534 |
+
|
| 535 |
+
Run a small batch inference job:
|
| 536 |
+
|
| 537 |
+
```bash
|
| 538 |
+
python batch_infer.py \
|
| 539 |
+
--model . \
|
| 540 |
+
--attn la_flash \
|
| 541 |
+
--scheduler pipeline \
|
| 542 |
+
--batch-size 4 \
|
| 543 |
+
--image assets/pointing.png \
|
| 544 |
+
--query "the object being pointed at"
|
| 545 |
+
```
|
| 546 |
+
|
| 547 |
+
The batched sparse-plan decode runtime is intended for inference/evaluation and
|
| 548 |
+
does not support the training `labels` path. Training remains on the
|
| 549 |
+
MagiAttention backend.
|
| 550 |
+
|
| 551 |
## Ethical Considerations:
|
| 552 |
NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal model team to ensure this model meets requirements for the relevant industry and use case and addresses unforeseen product misuse.
|
| 553 |
|
| 554 |
Please make sure you have proper rights and permissions for all input image and video content; if image or video includes people, personal health information, or intellectual property, the image or video generated will not blur or maintain proportions of image subjects included.
|
| 555 |
|
| 556 |
+
Please report model quality, risk, security vulnerabilities or NVIDIA AI Concerns [here](https://app.intigriti.com/programs/nvidia/nvidiavdp/detail).
|
batch_infer.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Minimal batch inference CLI for the LocateAnything-3B release code.
|
| 3 |
+
|
| 4 |
+
Examples:
|
| 5 |
+
python batch_infer.py --model /path/to/LocateAnything-3B --attn sdpa \
|
| 6 |
+
--image demo.jpg --query "person</c>car"
|
| 7 |
+
|
| 8 |
+
python batch_infer.py --requests requests.jsonl --batch-size 16 --attn la_flash
|
| 9 |
+
|
| 10 |
+
Each JSONL request should contain {"image": "/path/to.jpg", "query": "person</c>car"}.
|
| 11 |
+
"""
|
| 12 |
+
import argparse
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
from PIL import Image
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _attn_arg(value):
|
| 21 |
+
mode = (value or "sdpa").strip().lower().replace("-", "_")
|
| 22 |
+
aliases = {
|
| 23 |
+
"": "sdpa",
|
| 24 |
+
"manual": "eager",
|
| 25 |
+
"torch": "eager",
|
| 26 |
+
"torch_eager": "eager",
|
| 27 |
+
"torch_sdpa": "sdpa",
|
| 28 |
+
"flash": "la_flash",
|
| 29 |
+
"la_flash": "la_flash",
|
| 30 |
+
"kernel": "la_flash",
|
| 31 |
+
"cuda": "la_flash",
|
| 32 |
+
"range": "la_flash",
|
| 33 |
+
"range_attention": "la_flash",
|
| 34 |
+
}
|
| 35 |
+
mode = aliases.get(mode, mode)
|
| 36 |
+
if mode not in {"sdpa", "eager", "magi", "la_flash"}:
|
| 37 |
+
raise argparse.ArgumentTypeError(
|
| 38 |
+
f"--attn must be one of sdpa, eager, magi, la_flash; got {value!r}"
|
| 39 |
+
)
|
| 40 |
+
return mode
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _load_requests(args):
|
| 44 |
+
requests = []
|
| 45 |
+
if args.requests:
|
| 46 |
+
with open(args.requests, "r", encoding="utf-8") as f:
|
| 47 |
+
for line in f:
|
| 48 |
+
if not line.strip():
|
| 49 |
+
continue
|
| 50 |
+
row = json.loads(line)
|
| 51 |
+
requests.append((row["image"], row["query"]))
|
| 52 |
+
if args.image or args.query:
|
| 53 |
+
if len(args.image or []) != len(args.query or []):
|
| 54 |
+
raise ValueError("--image and --query must appear the same number of times")
|
| 55 |
+
requests.extend(zip(args.image, args.query))
|
| 56 |
+
if not requests:
|
| 57 |
+
raise ValueError("provide --requests JSONL or at least one --image/--query pair")
|
| 58 |
+
return requests
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def main():
|
| 62 |
+
ap = argparse.ArgumentParser()
|
| 63 |
+
ap.add_argument("--requests", help="JSONL file with image/query fields")
|
| 64 |
+
ap.add_argument("--image", action="append", help="Image path; repeat with --query")
|
| 65 |
+
ap.add_argument("--query", action="append", help="Category query, e.g. person</c>car")
|
| 66 |
+
ap.add_argument("--model", default=os.environ.get("LA_FLASH_MODEL", "nvidia/LocateAnything-3B"))
|
| 67 |
+
ap.add_argument("--attn", type=_attn_arg, default=os.environ.get("LA_FLASH_ATTN", "sdpa"),
|
| 68 |
+
help="LLM attention backend: sdpa, eager, magi, or la_flash")
|
| 69 |
+
ap.add_argument("--vision-attn", default=os.environ.get("LA_FLASH_VISION_ATTN", "auto"),
|
| 70 |
+
choices=["auto", "flash_attention_2", "sdpa", "eager"])
|
| 71 |
+
ap.add_argument("--batch-size", type=int, default=1)
|
| 72 |
+
ap.add_argument("--scheduler", default=os.environ.get("LA_FLASH_HYBRID_SCHEDULER", "eager"),
|
| 73 |
+
choices=["eager", "hold_ar", "ar_first", "pipeline", "adaptive"])
|
| 74 |
+
ap.add_argument("--group-size", type=int, default=int(os.environ.get("LA_FLASH_HYBRID_GROUP_SIZE", "0")))
|
| 75 |
+
ap.add_argument("--max-new-tokens", type=int, default=2048)
|
| 76 |
+
ap.add_argument("--temperature", type=float, default=0.7)
|
| 77 |
+
ap.add_argument("--top-p", type=float, default=0.9)
|
| 78 |
+
ap.add_argument("--top-k", type=int, default=0)
|
| 79 |
+
ap.add_argument("--repetition-penalty", type=float, default=1.1)
|
| 80 |
+
ap.add_argument("--strict-attn", action="store_true",
|
| 81 |
+
help="Fail instead of falling back to SDPA if magi/la_flash is unavailable")
|
| 82 |
+
ap.add_argument("--out", default="", help="Optional output JSONL path; stdout if omitted")
|
| 83 |
+
args = ap.parse_args()
|
| 84 |
+
args.attn = _attn_arg(args.attn)
|
| 85 |
+
|
| 86 |
+
os.environ["LA_FLASH_MODEL"] = args.model
|
| 87 |
+
os.environ["LA_FLASH_ATTN"] = args.attn
|
| 88 |
+
os.environ["LA_FLASH_VISION_ATTN"] = args.vision_attn
|
| 89 |
+
os.environ["LA_FLASH_HYBRID_SCHEDULER"] = args.scheduler
|
| 90 |
+
os.environ["LA_FLASH_HYBRID_GROUP_SIZE"] = str(args.group_size)
|
| 91 |
+
if args.strict_attn:
|
| 92 |
+
os.environ["LA_FLASH_STRICT_ATTN"] = "1"
|
| 93 |
+
|
| 94 |
+
from batch_utils import generate_batch_hybrid, get_last_hybrid_stats, load
|
| 95 |
+
from batch_utils.hybrid_runtime import load_pil
|
| 96 |
+
|
| 97 |
+
requests = _load_requests(args)
|
| 98 |
+
load()
|
| 99 |
+
|
| 100 |
+
writer = open(args.out, "w", encoding="utf-8") if args.out else None
|
| 101 |
+
try:
|
| 102 |
+
for start in range(0, len(requests), max(1, args.batch_size)):
|
| 103 |
+
chunk = requests[start:start + max(1, args.batch_size)]
|
| 104 |
+
pairs = [(load_pil(image), query) for image, query in chunk]
|
| 105 |
+
texts = generate_batch_hybrid(
|
| 106 |
+
pairs,
|
| 107 |
+
temperature=args.temperature,
|
| 108 |
+
top_p=None if args.top_p < 0 else args.top_p,
|
| 109 |
+
top_k=None if args.top_k <= 0 else args.top_k,
|
| 110 |
+
repetition_penalty=args.repetition_penalty,
|
| 111 |
+
max_new_tokens=args.max_new_tokens,
|
| 112 |
+
scheduler=args.scheduler,
|
| 113 |
+
group_size=args.group_size,
|
| 114 |
+
)
|
| 115 |
+
stats = get_last_hybrid_stats()
|
| 116 |
+
for (image, query), text in zip(chunk, texts):
|
| 117 |
+
row = {"image": str(Path(image)), "query": query, "raw_response": text, "stats": stats}
|
| 118 |
+
line = json.dumps(row, ensure_ascii=False)
|
| 119 |
+
if writer:
|
| 120 |
+
writer.write(line + "\n")
|
| 121 |
+
else:
|
| 122 |
+
print(line, flush=True)
|
| 123 |
+
finally:
|
| 124 |
+
if writer:
|
| 125 |
+
writer.close()
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
main()
|
batch_utils/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Batch Utils
|
| 2 |
+
|
| 3 |
+
`batch_utils` contains the optional batched hybrid generation runtime for
|
| 4 |
+
LocateAnything. It keeps the model loading, tokenization, image feature caching,
|
| 5 |
+
sampling, and scheduler code used by `batch_infer.py` and the detection
|
| 6 |
+
experiments.
|
| 7 |
+
|
| 8 |
+
## Runtime Modes
|
| 9 |
+
|
| 10 |
+
- `LA_FLASH_ATTN=sdpa`: stock PyTorch SDPA path.
|
| 11 |
+
- `LA_FLASH_ATTN=eager`: eager attention path for debugging.
|
| 12 |
+
- `LA_FLASH_ATTN=magi`: MagiAttention path when MagiAttention is installed.
|
| 13 |
+
- `LA_FLASH_ATTN=la_flash`: LA Flash sparse range backend
|
| 14 |
+
from `kernel_utils`.
|
| 15 |
+
|
| 16 |
+
## Common Knobs
|
| 17 |
+
|
| 18 |
+
| Variable | Default | Meaning |
|
| 19 |
+
| --- | --- | --- |
|
| 20 |
+
| `LA_FLASH_MODEL` | `nvidia/LocateAnything-3B` | HF model id or local model directory. |
|
| 21 |
+
| `LA_FLASH_ATTN` | `sdpa` | LLM attention backend. |
|
| 22 |
+
| `LA_FLASH_VISION_ATTN` | `auto` | Vision encoder attention: `auto`, `flash_attention_2`, `sdpa`, or `eager`. |
|
| 23 |
+
| `LA_FLASH_STRICT_ATTN` | `0` | Set `1` to fail instead of falling back to SDPA. |
|
| 24 |
+
| `LA_FLASH_HYBRID_SCHEDULER` | `eager` | Hybrid decode scheduler. |
|
| 25 |
+
| `LA_FLASH_HYBRID_GROUP_SIZE` | `0` | Scheduler group size; `0` lets the runtime decide. |
|
| 26 |
+
| `LA_FLASH_VISION_ENCODE_BATCH_SIZE` | `8` | Maximum images per MoonViT encode micro-batch. |
|
| 27 |
+
| `LA_FLASH_KV_PACK_TOKEN_BUDGET` | `0` | Optional KV packing memory cap for long-tail batches. |
|
| 28 |
+
| `LA_FLASH_DENSE_BACKEND` | `sdpa` | Dense worker/prefill attention backend. Keep this as `sdpa`; LA Flash is used for sparse range plans. |
|
| 29 |
+
| `LA_FLASH_SEGMENT_FASTPATH` | `auto` | Sparse MTP decode uses FlashAttention varlen multi-segment merge by default. |
|
| 30 |
+
|
| 31 |
+
## CLI Example
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
python batch_infer.py \
|
| 35 |
+
--model nvidia/LocateAnything-3B \
|
| 36 |
+
--attn la_flash \
|
| 37 |
+
--scheduler pipeline \
|
| 38 |
+
--batch-size 4 \
|
| 39 |
+
--image /path/to/image.jpg \
|
| 40 |
+
--query "person</c>car"
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
For JSONL input, each row should contain:
|
| 44 |
+
|
| 45 |
+
```json
|
| 46 |
+
{"image": "/path/to/image.jpg", "query": "person</c>car"}
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
## Training Boundary
|
| 50 |
+
|
| 51 |
+
This package is for inference and evaluation. Training remains on the
|
| 52 |
+
MagiAttention backend; the batched sparse-plan decode runtime does not support
|
| 53 |
+
the `labels` training path.
|
batch_utils/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hybrid batched inference helpers for NVIDIA LocateAnything-3B."""
|
| 2 |
+
from .hybrid_runtime import load, load_pil
|
| 3 |
+
from .engine_hybrid import generate_batch_hybrid, generate_batch_grouped_hybrid, get_last_hybrid_stats
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
"load",
|
| 7 |
+
"load_pil",
|
| 8 |
+
"generate_batch_hybrid",
|
| 9 |
+
"generate_batch_grouped_hybrid",
|
| 10 |
+
"get_last_hybrid_stats",
|
| 11 |
+
]
|
| 12 |
+
__version__ = "0.1.0"
|
batch_utils/engine_hybrid.py
ADDED
|
@@ -0,0 +1,1357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Batched hybrid-mode generation for LocateAnything-3B.
|
| 2 |
+
|
| 3 |
+
This module keeps the stock hybrid state machine:
|
| 4 |
+
|
| 5 |
+
MTP -> error_box -> AR
|
| 6 |
+
AR -> box_end_ar -> MTP
|
| 7 |
+
|
| 8 |
+
Rows in a batch may be in different modes. The decode loop therefore stores
|
| 9 |
+
per-row KV caches, packs rows with the same mode for one forward call, then
|
| 10 |
+
unpacks the clean KV back per row.
|
| 11 |
+
"""
|
| 12 |
+
import copy
|
| 13 |
+
import importlib
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
from .hybrid_runtime import (
|
| 20 |
+
ATTN_MODE,
|
| 21 |
+
AR_BATCH_SAN,
|
| 22 |
+
BATCH_SAN,
|
| 23 |
+
DEV,
|
| 24 |
+
N_FUTURE,
|
| 25 |
+
_encode_images,
|
| 26 |
+
_helpers,
|
| 27 |
+
_pad_generated,
|
| 28 |
+
_set_llm_mode,
|
| 29 |
+
_tokenize,
|
| 30 |
+
_tokenize_cached_image,
|
| 31 |
+
build_magi_scheduler_ranges,
|
| 32 |
+
language_model_forward,
|
| 33 |
+
load,
|
| 34 |
+
sample_next_tokens_batched,
|
| 35 |
+
sample_tokens_batched,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
README_MAX_NEW_TOKENS = 2048
|
| 40 |
+
README_TEMPERATURE = 0.7
|
| 41 |
+
README_TOP_P = 0.9
|
| 42 |
+
README_REPETITION_PENALTY = 1.1
|
| 43 |
+
|
| 44 |
+
_LAST_HYBRID_STATS = None
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _row_len(kv):
|
| 48 |
+
return kv[0][0].shape[2]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _pack_stock_kv_rows(kv_rows, rows, dev):
|
| 52 |
+
"""Left-pad per-row real-token KV caches for stock-style decoding."""
|
| 53 |
+
lengths = [0 if kv_rows[r] is None else _row_len(kv_rows[r]) for r in rows]
|
| 54 |
+
kmax = max(lengths) if lengths else 0
|
| 55 |
+
if kmax == 0:
|
| 56 |
+
return None, torch.zeros((len(rows), 0), dtype=torch.long, device=dev), lengths, 0
|
| 57 |
+
|
| 58 |
+
ref = next(kv_rows[r] for r in rows if kv_rows[r] is not None)
|
| 59 |
+
packed = []
|
| 60 |
+
for layer in range(len(ref)):
|
| 61 |
+
ref_k, ref_v = ref[layer]
|
| 62 |
+
ks, vs = [], []
|
| 63 |
+
for r, length in zip(rows, lengths):
|
| 64 |
+
if length == 0:
|
| 65 |
+
k = ref_k.new_zeros((1, ref_k.shape[1], kmax, ref_k.shape[3]))
|
| 66 |
+
v = ref_v.new_zeros((1, ref_v.shape[1], kmax, ref_v.shape[3]))
|
| 67 |
+
else:
|
| 68 |
+
k, v = kv_rows[r][layer]
|
| 69 |
+
if length < kmax:
|
| 70 |
+
pad_shape = (1, k.shape[1], kmax - length, k.shape[3])
|
| 71 |
+
k = torch.cat([k.new_zeros(pad_shape), k], dim=2)
|
| 72 |
+
v = torch.cat([v.new_zeros(pad_shape), v], dim=2)
|
| 73 |
+
ks.append(k)
|
| 74 |
+
vs.append(v)
|
| 75 |
+
packed.append((torch.cat(ks, dim=0), torch.cat(vs, dim=0)))
|
| 76 |
+
|
| 77 |
+
kvalid = torch.zeros((len(rows), kmax), dtype=torch.long, device=dev)
|
| 78 |
+
for i, length in enumerate(lengths):
|
| 79 |
+
if length:
|
| 80 |
+
kvalid[i, kmax - length :] = 1
|
| 81 |
+
return tuple(packed), kvalid, lengths, kmax
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _unpack_stock_after_forward(out_kv, local_row, old_len, uncached_len, kmax, umax):
|
| 85 |
+
"""Keep old real KV plus the right-aligned uncached real tokens; drop pads/window."""
|
| 86 |
+
out = []
|
| 87 |
+
u0 = kmax + (umax - uncached_len)
|
| 88 |
+
u1 = kmax + umax
|
| 89 |
+
for k, v in out_kv:
|
| 90 |
+
parts_k, parts_v = [], []
|
| 91 |
+
if old_len:
|
| 92 |
+
parts_k.append(k[local_row : local_row + 1, :, kmax - old_len : kmax, :])
|
| 93 |
+
parts_v.append(v[local_row : local_row + 1, :, kmax - old_len : kmax, :])
|
| 94 |
+
if uncached_len:
|
| 95 |
+
parts_k.append(k[local_row : local_row + 1, :, u0:u1, :])
|
| 96 |
+
parts_v.append(v[local_row : local_row + 1, :, u0:u1, :])
|
| 97 |
+
out.append((torch.cat(parts_k, dim=2).contiguous(),
|
| 98 |
+
torch.cat(parts_v, dim=2).contiguous()))
|
| 99 |
+
return tuple(out)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _mk_generate_kwargs(temperature, top_p, top_k, repetition_penalty, row_temp=None):
|
| 103 |
+
t = temperature if row_temp is None else row_temp
|
| 104 |
+
gk = {"repetition_penalty": repetition_penalty, "generation_mode": "hybrid"}
|
| 105 |
+
if t and t > 0:
|
| 106 |
+
gk["temperature"] = t
|
| 107 |
+
if top_p is not None:
|
| 108 |
+
gk["top_p"] = top_p
|
| 109 |
+
if top_k is not None:
|
| 110 |
+
gk["top_k"] = top_k
|
| 111 |
+
return gk
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _classify_ar_token(token_val, tids):
|
| 115 |
+
if token_val == tids["box_end_token_id"]:
|
| 116 |
+
return "box_end_ar"
|
| 117 |
+
if tids["coord_start_token_id"] <= token_val <= tids["coord_end_token_id"]:
|
| 118 |
+
return "coord_ar"
|
| 119 |
+
if token_val == tids["none_token_id"]:
|
| 120 |
+
return "coord_ar"
|
| 121 |
+
return "im_end"
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _env_flag(name, default=False):
|
| 125 |
+
val = os.environ.get(name)
|
| 126 |
+
if val is None:
|
| 127 |
+
return default
|
| 128 |
+
return val.lower() not in {"0", "false", "no", "off", ""}
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _env_int(name, default):
|
| 132 |
+
val = os.environ.get(name)
|
| 133 |
+
if val is None or val == "":
|
| 134 |
+
return default
|
| 135 |
+
return int(val)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _kv_pack_token_budget():
|
| 139 |
+
return max(0, _env_int("LA_FLASH_KV_PACK_TOKEN_BUDGET", 0))
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _debug_enabled(debug):
|
| 143 |
+
return _env_flag("LA_FLASH_DEBUG", False) if debug is None else bool(debug)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def _new_hybrid_stats(total_rows, scheduler, group_size, hold_max_steps, adaptive_hold_mtp_max=0):
|
| 147 |
+
return {
|
| 148 |
+
"scheduler": scheduler,
|
| 149 |
+
"requested_group_size": int(group_size or 0),
|
| 150 |
+
"hold_max_steps": int(hold_max_steps),
|
| 151 |
+
"adaptive_hold_mtp_max": int(adaptive_hold_mtp_max),
|
| 152 |
+
"input_batches": 1,
|
| 153 |
+
"input_rows": int(total_rows),
|
| 154 |
+
"groups": 0,
|
| 155 |
+
"group_sizes": [],
|
| 156 |
+
"decode_loops": 0,
|
| 157 |
+
"mixed_mode_cycles": 0,
|
| 158 |
+
"eager_mtp_then_ar_cycles": 0,
|
| 159 |
+
"ar_first_cycles": 0,
|
| 160 |
+
"pipeline_ar_after_mtp_cycles": 0,
|
| 161 |
+
"adaptive_hold_cycles": 0,
|
| 162 |
+
"adaptive_ar_first_cycles": 0,
|
| 163 |
+
"hold_ar_steps": 0,
|
| 164 |
+
"hold_ar_held_mtp_rows": 0,
|
| 165 |
+
"hold_ar_limit_mtp_forwards": 0,
|
| 166 |
+
"mtp_forwards": 0,
|
| 167 |
+
"ar_forwards": 0,
|
| 168 |
+
"mtp_forward_rows": 0,
|
| 169 |
+
"ar_forward_rows": 0,
|
| 170 |
+
"mtp_forward_query_tokens": 0,
|
| 171 |
+
"ar_forward_query_tokens": 0,
|
| 172 |
+
"max_mtp_forward_rows": 0,
|
| 173 |
+
"max_ar_forward_rows": 0,
|
| 174 |
+
"mtp_max_uncached_len": 0,
|
| 175 |
+
"ar_max_uncached_len": 0,
|
| 176 |
+
"mtp_forward_row_hist": {},
|
| 177 |
+
"ar_forward_row_hist": {},
|
| 178 |
+
"prompt_prefill_mode": _hybrid_prefill_mode(),
|
| 179 |
+
"prompt_prefill_forwards": 0,
|
| 180 |
+
"prompt_prefill_forward_rows": 0,
|
| 181 |
+
"prompt_prefill_forward_query_tokens": 0,
|
| 182 |
+
"prompt_prefill_real_tokens": 0,
|
| 183 |
+
"prompt_prefill_shared_groups": 0,
|
| 184 |
+
"prompt_prefill_shared_rows": 0,
|
| 185 |
+
"prompt_prefill_shared_saved_tokens": 0,
|
| 186 |
+
"kv_bucket_splits": 0,
|
| 187 |
+
"kv_bucket_groups": 0,
|
| 188 |
+
"kv_bucket_max_packed_tokens": 0,
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _set_last_hybrid_stats(stats):
|
| 193 |
+
global _LAST_HYBRID_STATS
|
| 194 |
+
_LAST_HYBRID_STATS = copy.deepcopy(stats) if stats is not None else None
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def get_last_hybrid_stats():
|
| 198 |
+
"""Return scheduler/forward statistics from the most recent hybrid batch."""
|
| 199 |
+
return copy.deepcopy(_LAST_HYBRID_STATS)
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def _record_group_stats(stats, bsz):
|
| 203 |
+
if stats is None:
|
| 204 |
+
return
|
| 205 |
+
stats["groups"] += 1
|
| 206 |
+
stats["group_sizes"].append(int(bsz))
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _bump_hist(hist, val):
|
| 210 |
+
key = str(int(val))
|
| 211 |
+
hist[key] = int(hist.get(key, 0)) + 1
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def _record_forward_stats(stats, kind, rows, q_len, uncached_lens):
|
| 215 |
+
if stats is None:
|
| 216 |
+
return
|
| 217 |
+
prefix = "mtp" if kind == "mtp" else "ar"
|
| 218 |
+
nrows = int(len(rows))
|
| 219 |
+
q_len = int(q_len)
|
| 220 |
+
stats[f"{prefix}_forwards"] += 1
|
| 221 |
+
stats[f"{prefix}_forward_rows"] += nrows
|
| 222 |
+
stats[f"{prefix}_forward_query_tokens"] += nrows * q_len
|
| 223 |
+
stats[f"max_{prefix}_forward_rows"] = max(stats[f"max_{prefix}_forward_rows"], nrows)
|
| 224 |
+
stats[f"{prefix}_max_uncached_len"] = max(
|
| 225 |
+
stats[f"{prefix}_max_uncached_len"],
|
| 226 |
+
max((int(x) for x in uncached_lens), default=0),
|
| 227 |
+
)
|
| 228 |
+
_bump_hist(stats[f"{prefix}_forward_row_hist"], nrows)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def _record_prefill_stats(stats, rows, q_len, real_tokens, shared_groups=0, shared_rows=0, saved_tokens=0):
|
| 232 |
+
if stats is None:
|
| 233 |
+
return
|
| 234 |
+
nrows = int(rows)
|
| 235 |
+
stats["prompt_prefill_forwards"] += 1
|
| 236 |
+
stats["prompt_prefill_forward_rows"] += nrows
|
| 237 |
+
stats["prompt_prefill_forward_query_tokens"] += nrows * int(q_len)
|
| 238 |
+
stats["prompt_prefill_real_tokens"] += int(real_tokens)
|
| 239 |
+
stats["prompt_prefill_shared_groups"] += int(shared_groups)
|
| 240 |
+
stats["prompt_prefill_shared_rows"] += int(shared_rows)
|
| 241 |
+
stats["prompt_prefill_shared_saved_tokens"] += int(saved_tokens)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _split_rows_by_kv_budget(rows, kv_rows):
|
| 245 |
+
"""Keep dense left-padded KV packs bounded when a few rows become long tails."""
|
| 246 |
+
budget = _kv_pack_token_budget()
|
| 247 |
+
if budget <= 0 or len(rows) <= 1:
|
| 248 |
+
return [rows]
|
| 249 |
+
lengths = [0 if kv_rows[r] is None else _row_len(kv_rows[r]) for r in rows]
|
| 250 |
+
if not lengths or max(lengths) * len(rows) <= budget:
|
| 251 |
+
return [rows]
|
| 252 |
+
|
| 253 |
+
groups = []
|
| 254 |
+
current = []
|
| 255 |
+
current_max = 0
|
| 256 |
+
for row, length in sorted(zip(rows, lengths), key=lambda item: item[1]):
|
| 257 |
+
next_max = max(current_max, int(length))
|
| 258 |
+
if current and next_max * (len(current) + 1) > budget:
|
| 259 |
+
groups.append(current)
|
| 260 |
+
current = [row]
|
| 261 |
+
current_max = int(length)
|
| 262 |
+
else:
|
| 263 |
+
current.append(row)
|
| 264 |
+
current_max = next_max
|
| 265 |
+
if current:
|
| 266 |
+
groups.append(current)
|
| 267 |
+
return groups or [rows]
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _record_kv_bucket_stats(stats, groups, kv_rows):
|
| 271 |
+
if stats is None:
|
| 272 |
+
return
|
| 273 |
+
max_packed = 0
|
| 274 |
+
for group in groups:
|
| 275 |
+
if not group:
|
| 276 |
+
continue
|
| 277 |
+
kmax = max((0 if kv_rows[r] is None else _row_len(kv_rows[r])) for r in group)
|
| 278 |
+
max_packed = max(max_packed, int(kmax) * len(group))
|
| 279 |
+
stats["kv_bucket_max_packed_tokens"] = max(stats["kv_bucket_max_packed_tokens"], max_packed)
|
| 280 |
+
if len(groups) > 1:
|
| 281 |
+
stats["kv_bucket_splits"] += 1
|
| 282 |
+
stats["kv_bucket_groups"] += len(groups)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def _hybrid_scheduler(scheduler):
|
| 286 |
+
val = os.environ.get("LA_FLASH_HYBRID_SCHEDULER", "eager") if scheduler is None else scheduler
|
| 287 |
+
val = str(val).strip().lower()
|
| 288 |
+
aliases = {
|
| 289 |
+
"": "eager",
|
| 290 |
+
"default": "eager",
|
| 291 |
+
"normal": "eager",
|
| 292 |
+
"hold": "hold_ar",
|
| 293 |
+
"hold-ar": "hold_ar",
|
| 294 |
+
"hold_mtp": "hold_ar",
|
| 295 |
+
"hold-mtp": "hold_ar",
|
| 296 |
+
"repair_first": "ar_first",
|
| 297 |
+
"repair-first": "ar_first",
|
| 298 |
+
"ar-first": "ar_first",
|
| 299 |
+
}
|
| 300 |
+
val = aliases.get(val, val)
|
| 301 |
+
if val not in {"eager", "hold_ar", "ar_first", "pipeline", "adaptive"}:
|
| 302 |
+
raise ValueError("scheduler must be one of: eager, hold_ar, ar_first, pipeline, adaptive")
|
| 303 |
+
return val
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def _hybrid_group_size(group_size):
|
| 307 |
+
if group_size is None:
|
| 308 |
+
return max(0, _env_int("LA_FLASH_HYBRID_GROUP_SIZE", 0))
|
| 309 |
+
return max(0, int(group_size))
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def _hybrid_prefill_mode():
|
| 313 |
+
val = os.environ.get("LA_FLASH_HYBRID_PREFILL", "shared").strip().lower()
|
| 314 |
+
aliases = {
|
| 315 |
+
"0": "none",
|
| 316 |
+
"false": "none",
|
| 317 |
+
"off": "none",
|
| 318 |
+
"legacy": "none",
|
| 319 |
+
"1": "per_row",
|
| 320 |
+
"true": "per_row",
|
| 321 |
+
"on": "per_row",
|
| 322 |
+
"single": "per_row",
|
| 323 |
+
"row": "per_row",
|
| 324 |
+
"rows": "per_row",
|
| 325 |
+
"batched": "batch",
|
| 326 |
+
"prefix": "shared",
|
| 327 |
+
"shared_prefix": "shared",
|
| 328 |
+
"shared-image": "shared",
|
| 329 |
+
"shared_image": "shared",
|
| 330 |
+
"vision": "shared",
|
| 331 |
+
}
|
| 332 |
+
val = aliases.get(val, val)
|
| 333 |
+
if val not in {"none", "per_row", "batch", "shared"}:
|
| 334 |
+
raise ValueError("LA_FLASH_HYBRID_PREFILL must be one of none, per_row, batch, shared")
|
| 335 |
+
return val
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def _tolist(t):
|
| 339 |
+
return t.detach().cpu().tolist()
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def _safe_decode_rows(tok, input_ids):
|
| 343 |
+
rows = []
|
| 344 |
+
for row in _tolist(input_ids):
|
| 345 |
+
try:
|
| 346 |
+
rows.append(tok.decode(torch.tensor(row), skip_special_tokens=False))
|
| 347 |
+
except Exception:
|
| 348 |
+
rows.append("<decode failed>")
|
| 349 |
+
return rows
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def _safe_decode_row(tok, row):
|
| 353 |
+
try:
|
| 354 |
+
return tok.decode(torch.tensor(row), skip_special_tokens=False)
|
| 355 |
+
except Exception:
|
| 356 |
+
return "<decode failed>"
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def _effective_allowed_mask(mask2d, q_len, past_len, mtp_window=False):
|
| 360 |
+
"""Readable 1/0 q-by-k mask derived from the 2D key-valid mask.
|
| 361 |
+
|
| 362 |
+
This mirrors the model path at a high level:
|
| 363 |
+
causal + padding columns, then the MTP window update
|
| 364 |
+
attn[-block:, -block:] = visible and attn[-block:, -block-1] = masked.
|
| 365 |
+
"""
|
| 366 |
+
rows = []
|
| 367 |
+
key_valid = mask2d.detach().cpu().bool()
|
| 368 |
+
total_len = int(key_valid.numel())
|
| 369 |
+
for qi in range(q_len):
|
| 370 |
+
q_abs = past_len + qi
|
| 371 |
+
row = []
|
| 372 |
+
for ki in range(total_len):
|
| 373 |
+
row.append(1 if bool(key_valid[ki]) and ki <= q_abs else 0)
|
| 374 |
+
rows.append(row)
|
| 375 |
+
|
| 376 |
+
if mtp_window and q_len >= N_FUTURE and total_len >= N_FUTURE:
|
| 377 |
+
q0 = q_len - N_FUTURE
|
| 378 |
+
k0 = total_len - N_FUTURE
|
| 379 |
+
for qi in range(q0, q_len):
|
| 380 |
+
for ki in range(k0, total_len):
|
| 381 |
+
rows[qi][ki] = 1
|
| 382 |
+
if k0 - 1 >= 0:
|
| 383 |
+
rows[qi][k0 - 1] = 0
|
| 384 |
+
return rows
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def _tail_matrix(mat, rows=None, cols=None):
|
| 388 |
+
if rows is not None:
|
| 389 |
+
mat = mat[-rows:]
|
| 390 |
+
if cols is not None:
|
| 391 |
+
mat = [row[-cols:] for row in mat]
|
| 392 |
+
return mat
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def _format_01_matrix(mat):
|
| 396 |
+
return "\n".join(" " + " ".join(str(int(v)) for v in row) for row in mat)
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def _safe_sdpa_mask_enabled():
|
| 400 |
+
return _env_flag("LA_FLASH_SDPA_SAFE_4D_MASK", True)
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
def _build_safe_sdpa_visible_mask(attention_mask_2d, input_ids, past_len, mtp_window=False):
|
| 404 |
+
"""Build a 4D 1/0 visible mask, with harmless visibility for all-masked pad queries.
|
| 405 |
+
|
| 406 |
+
The remote Qwen2 SDPA path uses a 2D key-valid mask and can create fully
|
| 407 |
+
masked query rows for left-padded, no-cache prefill. Those rows can produce
|
| 408 |
+
NaNs inside SDPA and later contaminate real tokens through masked K columns.
|
| 409 |
+
This 4D mask keeps real-token visibility identical, and only gives otherwise
|
| 410 |
+
all-masked query rows one valid fallback key so their activations stay finite.
|
| 411 |
+
"""
|
| 412 |
+
bsz, q_len = int(input_ids.shape[0]), int(input_ids.shape[1])
|
| 413 |
+
key_len = int(attention_mask_2d.shape[1])
|
| 414 |
+
dev = input_ids.device
|
| 415 |
+
key_valid = attention_mask_2d.to(dtype=torch.bool, device=dev)
|
| 416 |
+
key_idx = torch.arange(key_len, device=dev).view(1, 1, key_len)
|
| 417 |
+
q_abs = (past_len + torch.arange(q_len, device=dev)).view(1, q_len, 1)
|
| 418 |
+
visible = key_valid[:, None, :] & (key_idx <= q_abs)
|
| 419 |
+
|
| 420 |
+
if mtp_window and q_len >= N_FUTURE and key_len >= N_FUTURE:
|
| 421 |
+
k0 = key_len - N_FUTURE
|
| 422 |
+
visible[:, -N_FUTURE:, k0:key_len] = key_valid[:, None, k0:key_len]
|
| 423 |
+
blocked_k = k0 - 1
|
| 424 |
+
if blocked_k >= 0:
|
| 425 |
+
visible[:, -N_FUTURE:, blocked_k] = False
|
| 426 |
+
|
| 427 |
+
row_has_key = visible.any(dim=-1)
|
| 428 |
+
fallback_rows = int((~row_has_key).sum().item())
|
| 429 |
+
if fallback_rows:
|
| 430 |
+
for b in range(bsz):
|
| 431 |
+
valid = torch.nonzero(key_valid[b], as_tuple=False).flatten()
|
| 432 |
+
fallback = int(valid[0].item()) if valid.numel() else 0
|
| 433 |
+
missing = torch.nonzero(~row_has_key[b], as_tuple=False).flatten()
|
| 434 |
+
if missing.numel():
|
| 435 |
+
visible[b, missing, fallback] = True
|
| 436 |
+
|
| 437 |
+
mask = visible[:, None, :, :].to(dtype=torch.bfloat16)
|
| 438 |
+
try:
|
| 439 |
+
mask._la_flash_visible_mask = True
|
| 440 |
+
except Exception:
|
| 441 |
+
pass
|
| 442 |
+
return mask, fallback_rows
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def _mask_desc(mask):
|
| 446 |
+
if mask is None:
|
| 447 |
+
return "none"
|
| 448 |
+
if isinstance(mask, dict):
|
| 449 |
+
return "magi_ranges"
|
| 450 |
+
if hasattr(mask, "dim"):
|
| 451 |
+
return "4d_safe_sdpa" if mask.dim() == 4 else "2d_key_valid"
|
| 452 |
+
return type(mask).__name__
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def _forward_attention_mask(model, input_ids, attention_mask_2d, past_len, mtp_window=False, range_plan=False):
|
| 456 |
+
llm = model.language_model.model
|
| 457 |
+
if getattr(model, "_la_flash_requested_attn", ATTN_MODE) in {"magi", "la_flash"}:
|
| 458 |
+
range_plan = build_magi_scheduler_ranges(
|
| 459 |
+
model, attention_mask_2d, input_ids, past_len, mtp_window=mtp_window)
|
| 460 |
+
if range_plan is not None:
|
| 461 |
+
return range_plan, 0
|
| 462 |
+
needs_safe_pad = (
|
| 463 |
+
past_len == 0
|
| 464 |
+
and attention_mask_2d is not None
|
| 465 |
+
and attention_mask_2d.dim() == 2
|
| 466 |
+
and input_ids.shape[0] > 1
|
| 467 |
+
)
|
| 468 |
+
if (
|
| 469 |
+
getattr(llm, "_attn_implementation", None) == "sdpa"
|
| 470 |
+
and _safe_sdpa_mask_enabled()
|
| 471 |
+
and needs_safe_pad
|
| 472 |
+
and attention_mask_2d is not None
|
| 473 |
+
and attention_mask_2d.dim() == 2
|
| 474 |
+
):
|
| 475 |
+
return _build_safe_sdpa_visible_mask(attention_mask_2d, input_ids, past_len, mtp_window)
|
| 476 |
+
return attention_mask_2d, 0
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
def _actual_sdpa_allowed_masks(model, input_ids, attention_mask, past_len):
|
| 480 |
+
"""Recreate the remote Qwen2 SDPA 4D additive mask and return a 0/1 view."""
|
| 481 |
+
llm = model.language_model.model
|
| 482 |
+
mod = importlib.import_module(type(llm).__module__)
|
| 483 |
+
bsz, q_len = int(input_ids.shape[0]), int(input_ids.shape[1])
|
| 484 |
+
dummy = torch.empty(
|
| 485 |
+
(bsz, q_len, 1),
|
| 486 |
+
dtype=torch.bfloat16,
|
| 487 |
+
device=input_ids.device,
|
| 488 |
+
)
|
| 489 |
+
mask4 = mod._prepare_4d_causal_attention_mask(
|
| 490 |
+
attention_mask,
|
| 491 |
+
(bsz, q_len),
|
| 492 |
+
dummy,
|
| 493 |
+
past_len,
|
| 494 |
+
sliding_window=getattr(llm.config, "sliding_window", None),
|
| 495 |
+
)
|
| 496 |
+
remote_ar_decode = q_len == 1 or (
|
| 497 |
+
input_ids is not None and int(input_ids[0, -1].item()) != int(llm.text_mask_token_id)
|
| 498 |
+
)
|
| 499 |
+
if not remote_ar_decode and mask4 is not None and mask4.dim() == 4:
|
| 500 |
+
rows = []
|
| 501 |
+
for b in range(bsz):
|
| 502 |
+
rows.append(
|
| 503 |
+
mod.update_causal_mask_for_one_gen_window_2d(
|
| 504 |
+
input_ids[b],
|
| 505 |
+
mask4[b][0].clone(),
|
| 506 |
+
block_size=int(llm.block_size),
|
| 507 |
+
use_cache=True,
|
| 508 |
+
causal_attn=bool(getattr(llm, "causal_attn", False)),
|
| 509 |
+
).unsqueeze(0)
|
| 510 |
+
)
|
| 511 |
+
mask4 = torch.stack(rows, dim=0)
|
| 512 |
+
allowed = (mask4[:, 0] >= 0).to(torch.int8).detach().cpu().tolist()
|
| 513 |
+
return allowed, tuple(mask4.shape), remote_ar_decode
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
def _debug_magi_ranges(q_len, past_len, mtp_window=False):
|
| 517 |
+
kv_len = past_len + q_len
|
| 518 |
+
ar_decode = not mtp_window
|
| 519 |
+
if ar_decode:
|
| 520 |
+
return {
|
| 521 |
+
"q_ranges": [[0, q_len]],
|
| 522 |
+
"k_ranges": [[0, kv_len]],
|
| 523 |
+
"attn_type_map": ["CAUSAL"],
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
block = N_FUTURE
|
| 527 |
+
if not (0 < block <= q_len <= kv_len):
|
| 528 |
+
return {"error": f"invalid magi MTP shape: block={block}, q_len={q_len}, kv_len={kv_len}"}
|
| 529 |
+
|
| 530 |
+
prefix_len = kv_len - block
|
| 531 |
+
blocked_k = prefix_len - 1
|
| 532 |
+
q_ranges, k_ranges, attn_types = [], [], []
|
| 533 |
+
if q_len == kv_len:
|
| 534 |
+
if prefix_len > 0:
|
| 535 |
+
q_ranges.append([0, prefix_len])
|
| 536 |
+
k_ranges.append([0, prefix_len])
|
| 537 |
+
attn_types.append("CAUSAL")
|
| 538 |
+
if prefix_len > 0 and blocked_k > 0:
|
| 539 |
+
q_ranges.append([prefix_len, kv_len])
|
| 540 |
+
k_ranges.append([0, blocked_k])
|
| 541 |
+
attn_types.append("FULL")
|
| 542 |
+
q_ranges.append([prefix_len, kv_len])
|
| 543 |
+
k_ranges.append([prefix_len, kv_len])
|
| 544 |
+
attn_types.append("FULL")
|
| 545 |
+
else:
|
| 546 |
+
recompute = q_len - block
|
| 547 |
+
q_global_start = kv_len - q_len
|
| 548 |
+
for i in range(recompute):
|
| 549 |
+
g = q_global_start + i
|
| 550 |
+
q_ranges.append([i, i + 1])
|
| 551 |
+
k_ranges.append([0, g + 1])
|
| 552 |
+
attn_types.append("FULL")
|
| 553 |
+
q_win = [recompute, q_len]
|
| 554 |
+
if blocked_k > 0:
|
| 555 |
+
q_ranges.append(q_win)
|
| 556 |
+
k_ranges.append([0, blocked_k])
|
| 557 |
+
attn_types.append("FULL")
|
| 558 |
+
q_ranges.append(q_win)
|
| 559 |
+
k_ranges.append([prefix_len, kv_len])
|
| 560 |
+
attn_types.append("FULL")
|
| 561 |
+
|
| 562 |
+
return {"q_ranges": q_ranges, "k_ranges": k_ranges, "attn_type_map": attn_types}
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
def _print_debug_forward(label, model, tok, input_ids, attention_mask, position_ids,
|
| 566 |
+
past_len, mtp_window=False, extra=None, attention_impl="sdpa"):
|
| 567 |
+
print(f"\n========== LA Flash DEBUG {label} ==========", flush=True)
|
| 568 |
+
if extra:
|
| 569 |
+
for k, v in extra.items():
|
| 570 |
+
print(f"{k}: {v}", flush=True)
|
| 571 |
+
tail = int(os.environ.get("LA_FLASH_DEBUG_TAIL", "15"))
|
| 572 |
+
bsz, q_len = int(input_ids.shape[0]), int(input_ids.shape[1])
|
| 573 |
+
key_len = int(attention_mask.shape[1])
|
| 574 |
+
q_tail, k_tail = min(tail, q_len), min(tail, key_len)
|
| 575 |
+
print(
|
| 576 |
+
"shapes: "
|
| 577 |
+
f"input_ids={tuple(input_ids.shape)} "
|
| 578 |
+
f"position_ids={tuple(position_ids.shape)} "
|
| 579 |
+
f"attention_mask_key_valid={tuple(attention_mask.shape)} "
|
| 580 |
+
f"mask_2d_q_by_k=({bsz}, {q_len}, {key_len}) "
|
| 581 |
+
f"mask_2d_tail=({bsz}, {q_tail}, {k_tail}) "
|
| 582 |
+
f"past_len={past_len} q_len={q_len} "
|
| 583 |
+
f"mtp_window={mtp_window} ar_decode={not mtp_window}",
|
| 584 |
+
flush=True,
|
| 585 |
+
)
|
| 586 |
+
print(f"dtypes/devices: input_ids={input_ids.dtype}@{input_ids.device} position_ids={position_ids.dtype}@{position_ids.device} attention_mask={attention_mask.dtype}@{attention_mask.device}", flush=True)
|
| 587 |
+
print(f"attention_impl={attention_impl}", flush=True)
|
| 588 |
+
input_rows = _tolist(input_ids)
|
| 589 |
+
pos_rows = _tolist(position_ids)
|
| 590 |
+
print(f"tail_window_last={tail}", flush=True)
|
| 591 |
+
print(f"input_ids_tail.shape=({bsz}, {q_tail})", flush=True)
|
| 592 |
+
print(f"position_ids_tail.shape=({bsz}, {q_tail})", flush=True)
|
| 593 |
+
actual_sdpa = None
|
| 594 |
+
if attention_impl in {"sdpa", "eager", "la_flash"}:
|
| 595 |
+
try:
|
| 596 |
+
actual_sdpa = _actual_sdpa_allowed_masks(model, input_ids, attention_mask, past_len)
|
| 597 |
+
print(
|
| 598 |
+
f"actual_sdpa_4d_mask_shape={actual_sdpa[1]} "
|
| 599 |
+
f"remote_ar_decode={actual_sdpa[2]}",
|
| 600 |
+
flush=True,
|
| 601 |
+
)
|
| 602 |
+
except Exception as e:
|
| 603 |
+
print(f"actual_sdpa_4d_mask_debug_failed={type(e).__name__}: {e}", flush=True)
|
| 604 |
+
|
| 605 |
+
for b in range(input_ids.shape[0]):
|
| 606 |
+
ids_tail = input_rows[b][-tail:]
|
| 607 |
+
pos_tail = pos_rows[b][-tail:]
|
| 608 |
+
allowed = _effective_allowed_mask(attention_mask[b], input_ids.shape[1], past_len, mtp_window)
|
| 609 |
+
q_tail = min(tail, len(allowed))
|
| 610 |
+
k_tail = min(tail, len(allowed[0]) if allowed else 0)
|
| 611 |
+
allowed_tail = _tail_matrix(allowed, rows=q_tail, cols=k_tail)
|
| 612 |
+
print(f"batch_row={b} ar_decode={not mtp_window}", flush=True)
|
| 613 |
+
print(f"input_ids_tail[-{tail}:]: {ids_tail}", flush=True)
|
| 614 |
+
print(f"decoded_tail[-{tail}:]: {_safe_decode_row(tok, ids_tail)}", flush=True)
|
| 615 |
+
print(f"position_ids_tail[-{tail}:]: {pos_tail}", flush=True)
|
| 616 |
+
print(f"expected_mask_2d_tail[-{q_tail}:,-{k_tail}:].shape=({q_tail}, {k_tail})", flush=True)
|
| 617 |
+
print(_format_01_matrix(allowed_tail), flush=True)
|
| 618 |
+
if actual_sdpa is not None:
|
| 619 |
+
actual = actual_sdpa[0][b]
|
| 620 |
+
actual_tail = _tail_matrix(actual, rows=q_tail, cols=k_tail)
|
| 621 |
+
mismatch = sum(
|
| 622 |
+
int(allowed[qi][ki] != actual[qi][ki])
|
| 623 |
+
for qi in range(len(allowed))
|
| 624 |
+
for ki in range(len(allowed[qi]))
|
| 625 |
+
)
|
| 626 |
+
print(
|
| 627 |
+
f"actual_sdpa_mask_2d_tail[-{q_tail}:,-{k_tail}:].shape=({q_tail}, {k_tail})",
|
| 628 |
+
flush=True,
|
| 629 |
+
)
|
| 630 |
+
print(_format_01_matrix(actual_tail), flush=True)
|
| 631 |
+
print(f"expected_vs_actual_sdpa_mismatch_count={mismatch}", flush=True)
|
| 632 |
+
|
| 633 |
+
if _env_flag("LA_FLASH_DEBUG_FULL_MASK", False):
|
| 634 |
+
masks = [
|
| 635 |
+
_effective_allowed_mask(attention_mask[b], input_ids.shape[1], past_len, mtp_window)
|
| 636 |
+
for b in range(input_ids.shape[0])
|
| 637 |
+
]
|
| 638 |
+
print("effective_allowed_mask_q_by_k_FULL:", masks, flush=True)
|
| 639 |
+
if attention_impl == "magi":
|
| 640 |
+
if bsz == 1:
|
| 641 |
+
print(
|
| 642 |
+
"magi_ranges:",
|
| 643 |
+
_debug_magi_ranges(input_ids.shape[1], past_len, mtp_window),
|
| 644 |
+
flush=True,
|
| 645 |
+
)
|
| 646 |
+
else:
|
| 647 |
+
print(
|
| 648 |
+
"magi_ranges: built once per forward from the batched scheduler mask",
|
| 649 |
+
flush=True,
|
| 650 |
+
)
|
| 651 |
+
print(
|
| 652 |
+
"magi_ranges_single_row_template:",
|
| 653 |
+
_debug_magi_ranges(input_ids.shape[1], past_len, mtp_window),
|
| 654 |
+
flush=True,
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
|
| 658 |
+
def _common_prefix_len(prompt_ids, rows):
|
| 659 |
+
if not rows:
|
| 660 |
+
return 0
|
| 661 |
+
first = prompt_ids[rows[0]]
|
| 662 |
+
max_len = min(int(prompt_ids[r].numel()) for r in rows)
|
| 663 |
+
prefix_len = 0
|
| 664 |
+
for idx in range(max_len):
|
| 665 |
+
val = int(first[idx].item())
|
| 666 |
+
if all(int(prompt_ids[r][idx].item()) == val for r in rows[1:]):
|
| 667 |
+
prefix_len += 1
|
| 668 |
+
else:
|
| 669 |
+
break
|
| 670 |
+
return prefix_len
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def _prefill_shared_prefix_kv_rows(model, prompt_ids, vit_list, img_tok, pad, dev, stats=None, debug=False):
|
| 674 |
+
"""Cache one common prompt prefix per image-feature group.
|
| 675 |
+
|
| 676 |
+
Multi-category split repeats the same image feature tensor for each
|
| 677 |
+
category prompt. Token ids are identical through the image tokens and the
|
| 678 |
+
fixed prompt prefix, so we prefill that shared prefix once and let each
|
| 679 |
+
category row forward only its text suffix.
|
| 680 |
+
"""
|
| 681 |
+
bsz = len(prompt_ids)
|
| 682 |
+
kv_rows = [None] * bsz
|
| 683 |
+
cached_lens = [0] * bsz
|
| 684 |
+
groups = {}
|
| 685 |
+
for row, vit in enumerate(vit_list):
|
| 686 |
+
groups.setdefault(id(vit), []).append(row)
|
| 687 |
+
|
| 688 |
+
items = []
|
| 689 |
+
min_prefix_len = max(1, _env_int("LA_FLASH_SHARED_PREFILL_MIN_PREFIX", 64))
|
| 690 |
+
for rows in groups.values():
|
| 691 |
+
if len(rows) < 2:
|
| 692 |
+
continue
|
| 693 |
+
prefix_len = _common_prefix_len(prompt_ids, rows)
|
| 694 |
+
if prefix_len < min_prefix_len:
|
| 695 |
+
continue
|
| 696 |
+
prefix_ids = prompt_ids[rows[0]][:prefix_len]
|
| 697 |
+
image_token_count = int((prefix_ids == img_tok).sum().item())
|
| 698 |
+
if image_token_count != int(vit_list[rows[0]].shape[0]):
|
| 699 |
+
if debug:
|
| 700 |
+
print(
|
| 701 |
+
"LA Flash shared prefill skip group: "
|
| 702 |
+
f"rows={rows} prefix_len={prefix_len} "
|
| 703 |
+
f"image_tokens={image_token_count} visual_rows={int(vit_list[rows[0]].shape[0])}",
|
| 704 |
+
flush=True,
|
| 705 |
+
)
|
| 706 |
+
continue
|
| 707 |
+
items.append((rows, prefix_ids, vit_list[rows[0]]))
|
| 708 |
+
|
| 709 |
+
if not items:
|
| 710 |
+
return kv_rows, cached_lens
|
| 711 |
+
|
| 712 |
+
lengths = [int(ids.numel()) for _rows, ids, _vit in items]
|
| 713 |
+
pmax = max(lengths)
|
| 714 |
+
input_ids = torch.full((len(items), pmax), pad, dtype=torch.long, device=dev)
|
| 715 |
+
amask = torch.zeros((len(items), pmax), dtype=torch.long, device=dev)
|
| 716 |
+
pos = torch.ones((len(items), pmax), dtype=torch.long, device=dev)
|
| 717 |
+
for item_idx, (_rows, ids, _vit) in enumerate(items):
|
| 718 |
+
length = lengths[item_idx]
|
| 719 |
+
left = pmax - length
|
| 720 |
+
input_ids[item_idx, left:] = ids.to(dev)
|
| 721 |
+
amask[item_idx, left:] = 1
|
| 722 |
+
pos[item_idx, left:] = torch.arange(length, dtype=torch.long, device=dev)
|
| 723 |
+
|
| 724 |
+
visual_features = torch.cat([vit for _rows, _ids, vit in items], dim=0)
|
| 725 |
+
assert int((input_ids == img_tok).sum().item()) == visual_features.shape[0], \
|
| 726 |
+
"shared-prefix image-token count != supplied visual_features rows"
|
| 727 |
+
|
| 728 |
+
if debug:
|
| 729 |
+
group_sizes = [len(rows) for rows, _ids, _vit in items]
|
| 730 |
+
print(
|
| 731 |
+
"LA Flash hybrid shared prompt prefill "
|
| 732 |
+
f"groups={len(items)} group_sizes={group_sizes} prefix_lens={lengths}",
|
| 733 |
+
flush=True,
|
| 734 |
+
)
|
| 735 |
+
|
| 736 |
+
forward_mask, fallback_rows = _forward_attention_mask(
|
| 737 |
+
model, input_ids, amask, 0, mtp_window=False)
|
| 738 |
+
if debug and fallback_rows:
|
| 739 |
+
print(
|
| 740 |
+
"LA Flash hybrid shared prefill safe SDPA fallback "
|
| 741 |
+
f"query_rows={fallback_rows}",
|
| 742 |
+
flush=True,
|
| 743 |
+
)
|
| 744 |
+
forward_kwargs = dict(
|
| 745 |
+
input_ids=input_ids,
|
| 746 |
+
visual_features=visual_features,
|
| 747 |
+
image_token_index=img_tok,
|
| 748 |
+
attention_mask=forward_mask,
|
| 749 |
+
position_ids=pos,
|
| 750 |
+
past_key_values=None,
|
| 751 |
+
use_cache=True,
|
| 752 |
+
)
|
| 753 |
+
if isinstance(forward_mask, dict):
|
| 754 |
+
out = language_model_forward(model, **forward_kwargs, return_logits=False)
|
| 755 |
+
else:
|
| 756 |
+
out = model.language_model.model(**forward_kwargs)
|
| 757 |
+
|
| 758 |
+
real_tokens = sum(lengths)
|
| 759 |
+
shared_rows = sum(len(rows) for rows, _ids, _vit in items)
|
| 760 |
+
saved_tokens = sum((len(rows) - 1) * length for (rows, _ids, _vit), length in zip(items, lengths))
|
| 761 |
+
_record_prefill_stats(
|
| 762 |
+
stats,
|
| 763 |
+
rows=len(items),
|
| 764 |
+
q_len=pmax,
|
| 765 |
+
real_tokens=real_tokens,
|
| 766 |
+
shared_groups=len(items),
|
| 767 |
+
shared_rows=shared_rows,
|
| 768 |
+
saved_tokens=saved_tokens,
|
| 769 |
+
)
|
| 770 |
+
|
| 771 |
+
for item_idx, (rows, _ids, _vit) in enumerate(items):
|
| 772 |
+
prefix_len = lengths[item_idx]
|
| 773 |
+
prefix_kv = _unpack_stock_after_forward(out.past_key_values, item_idx, 0, prefix_len, 0, pmax)
|
| 774 |
+
for row in rows:
|
| 775 |
+
kv_rows[row] = prefix_kv
|
| 776 |
+
cached_lens[row] = prefix_len
|
| 777 |
+
|
| 778 |
+
return kv_rows, cached_lens
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
@torch.no_grad()
|
| 782 |
+
def _prefill_prompt_kv_rows(model, prompt_ids, vit_list, img_tok, pad, dev, mode, debug=False, stats=None):
|
| 783 |
+
"""Return per-row prompt KV caches and cached lengths.
|
| 784 |
+
|
| 785 |
+
``mode='none'`` preserves the legacy stock-like first MTP forward where the
|
| 786 |
+
whole prompt and the 6-token MTP window are forwarded together. The split
|
| 787 |
+
prefill modes keep prompt KV clean before the scheduler batches only short
|
| 788 |
+
suffix/window forwards, which avoids ragged prompt+window masking in the
|
| 789 |
+
first decode step.
|
| 790 |
+
"""
|
| 791 |
+
bsz = len(prompt_ids)
|
| 792 |
+
lengths = [int(p.numel()) for p in prompt_ids]
|
| 793 |
+
if mode == "none":
|
| 794 |
+
return [None] * bsz, [0] * bsz
|
| 795 |
+
|
| 796 |
+
base = model.language_model.model
|
| 797 |
+
if debug:
|
| 798 |
+
print(f"LA Flash hybrid prompt prefill mode={mode} rows={bsz} lengths={lengths}", flush=True)
|
| 799 |
+
|
| 800 |
+
if mode == "shared":
|
| 801 |
+
return _prefill_shared_prefix_kv_rows(
|
| 802 |
+
model, prompt_ids, vit_list, img_tok, pad, dev, stats=stats, debug=debug)
|
| 803 |
+
|
| 804 |
+
if mode == "per_row":
|
| 805 |
+
kv_rows = []
|
| 806 |
+
for b, ids in enumerate(prompt_ids):
|
| 807 |
+
ids = ids.to(dev).unsqueeze(0)
|
| 808 |
+
pos = torch.arange(ids.shape[1], dtype=torch.long, device=dev).unsqueeze(0)
|
| 809 |
+
out = base(
|
| 810 |
+
input_ids=ids,
|
| 811 |
+
visual_features=vit_list[b],
|
| 812 |
+
image_token_index=img_tok,
|
| 813 |
+
attention_mask=None,
|
| 814 |
+
position_ids=pos,
|
| 815 |
+
past_key_values=None,
|
| 816 |
+
use_cache=True,
|
| 817 |
+
)
|
| 818 |
+
kv_rows.append(out.past_key_values)
|
| 819 |
+
_record_prefill_stats(stats, rows=1, q_len=ids.shape[1], real_tokens=ids.shape[1])
|
| 820 |
+
return kv_rows, lengths
|
| 821 |
+
|
| 822 |
+
pmax = max(lengths)
|
| 823 |
+
input_ids = torch.full((bsz, pmax), pad, dtype=torch.long, device=dev)
|
| 824 |
+
amask = torch.zeros((bsz, pmax), dtype=torch.long, device=dev)
|
| 825 |
+
pos = torch.ones((bsz, pmax), dtype=torch.long, device=dev)
|
| 826 |
+
for b, ids in enumerate(prompt_ids):
|
| 827 |
+
left = pmax - lengths[b]
|
| 828 |
+
input_ids[b, left:] = ids.to(dev)
|
| 829 |
+
amask[b, left:] = 1
|
| 830 |
+
pos[b, left:] = torch.arange(lengths[b], dtype=torch.long, device=dev)
|
| 831 |
+
|
| 832 |
+
visual_features = torch.cat(vit_list, dim=0)
|
| 833 |
+
assert int((input_ids == img_tok).sum().item()) == visual_features.shape[0], \
|
| 834 |
+
"image-token count != supplied visual_features rows"
|
| 835 |
+
forward_mask, fallback_rows = _forward_attention_mask(
|
| 836 |
+
model, input_ids, amask, 0, mtp_window=False)
|
| 837 |
+
if debug and fallback_rows:
|
| 838 |
+
print(
|
| 839 |
+
"LA Flash hybrid batch prefill safe SDPA fallback "
|
| 840 |
+
f"query_rows={fallback_rows}",
|
| 841 |
+
flush=True,
|
| 842 |
+
)
|
| 843 |
+
forward_kwargs = dict(
|
| 844 |
+
input_ids=input_ids,
|
| 845 |
+
visual_features=visual_features,
|
| 846 |
+
image_token_index=img_tok,
|
| 847 |
+
attention_mask=forward_mask,
|
| 848 |
+
position_ids=pos,
|
| 849 |
+
past_key_values=None,
|
| 850 |
+
use_cache=True,
|
| 851 |
+
)
|
| 852 |
+
if isinstance(forward_mask, dict):
|
| 853 |
+
out = language_model_forward(model, **forward_kwargs, return_logits=False)
|
| 854 |
+
else:
|
| 855 |
+
out = base(**forward_kwargs)
|
| 856 |
+
_record_prefill_stats(stats, rows=bsz, q_len=pmax, real_tokens=sum(lengths))
|
| 857 |
+
kv_rows = [
|
| 858 |
+
_unpack_stock_after_forward(out.past_key_values, b, 0, lengths[b], 0, pmax)
|
| 859 |
+
for b in range(bsz)
|
| 860 |
+
]
|
| 861 |
+
return kv_rows, lengths
|
| 862 |
+
|
| 863 |
+
|
| 864 |
+
@torch.no_grad()
|
| 865 |
+
def generate_batch_hybrid(pairs, temperature=README_TEMPERATURE, top_p=README_TOP_P, top_k=None,
|
| 866 |
+
repetition_penalty=README_REPETITION_PENALTY,
|
| 867 |
+
max_new_tokens=README_MAX_NEW_TOKENS, temps=None,
|
| 868 |
+
debug=None, scheduler=None, group_size=None,
|
| 869 |
+
vision_features=None, _stats=None):
|
| 870 |
+
"""Batched stock-style LocateAnything-3B hybrid generation.
|
| 871 |
+
|
| 872 |
+
This mirrors ``model.generate(..., generation_mode='hybrid')``: each row
|
| 873 |
+
owns a full ``generated`` token stream plus a KV cache truncated to real
|
| 874 |
+
generated tokens before sampling. MTP forwards
|
| 875 |
+
``generated[cached_len:] + duplicate-last + mask*5``; AR forwards
|
| 876 |
+
``generated[cached_len:]``.
|
| 877 |
+
"""
|
| 878 |
+
tok, _, model = load()
|
| 879 |
+
san, hpat = _helpers()
|
| 880 |
+
tids = model.token_ids
|
| 881 |
+
img_tok = model.config.image_token_index
|
| 882 |
+
mask_tok = tids["default_mask_token_id"]
|
| 883 |
+
im_end = tids["im_end_token_id"]
|
| 884 |
+
pad = tok.pad_token_id if tok.pad_token_id is not None else im_end
|
| 885 |
+
dev = DEV
|
| 886 |
+
|
| 887 |
+
if not pairs:
|
| 888 |
+
return []
|
| 889 |
+
if temps is not None and len(temps) != len(pairs):
|
| 890 |
+
raise ValueError("temps must have the same length as pairs")
|
| 891 |
+
if vision_features is not None and len(vision_features) != len(pairs):
|
| 892 |
+
raise ValueError("vision_features must have the same length as pairs")
|
| 893 |
+
debug = _debug_enabled(debug)
|
| 894 |
+
scheduler = _hybrid_scheduler(scheduler)
|
| 895 |
+
group_size = _hybrid_group_size(group_size)
|
| 896 |
+
requested_attn = getattr(model, "_la_flash_requested_attn", ATTN_MODE)
|
| 897 |
+
use_magi = requested_attn == "magi"
|
| 898 |
+
prefill_mode = _hybrid_prefill_mode()
|
| 899 |
+
hold_max_steps = max(0, _env_int("LA_FLASH_HYBRID_HOLD_MAX_STEPS", 5))
|
| 900 |
+
adaptive_hold_mtp_max = max(0, _env_int("LA_FLASH_HYBRID_ADAPTIVE_HOLD_MTP_MAX", 3))
|
| 901 |
+
top_level_stats = _stats is None
|
| 902 |
+
if top_level_stats:
|
| 903 |
+
_stats = _new_hybrid_stats(
|
| 904 |
+
len(pairs), scheduler, group_size, hold_max_steps, adaptive_hold_mtp_max)
|
| 905 |
+
if os.environ.get("LA_FLASH_PLAN_STATS", "0") == "1":
|
| 906 |
+
model._la_flash_sparse_plan_stats = None
|
| 907 |
+
if group_size and len(pairs) > group_size:
|
| 908 |
+
outs = []
|
| 909 |
+
if debug:
|
| 910 |
+
print(
|
| 911 |
+
f"LA Flash hybrid grouped scheduling: total_rows={len(pairs)} "
|
| 912 |
+
f"group_size={group_size} scheduler={scheduler} hold_max_steps={hold_max_steps} "
|
| 913 |
+
f"adaptive_hold_mtp_max={adaptive_hold_mtp_max}",
|
| 914 |
+
flush=True,
|
| 915 |
+
)
|
| 916 |
+
for start in range(0, len(pairs), group_size):
|
| 917 |
+
end = min(start + group_size, len(pairs))
|
| 918 |
+
chunk_temps = temps[start:end] if temps is not None else None
|
| 919 |
+
chunk_vision_features = (
|
| 920 |
+
vision_features[start:end] if vision_features is not None else None
|
| 921 |
+
)
|
| 922 |
+
if debug:
|
| 923 |
+
print(f"LA Flash hybrid group rows=[{start}:{end}]", flush=True)
|
| 924 |
+
outs.extend(generate_batch_hybrid(
|
| 925 |
+
pairs[start:end],
|
| 926 |
+
temperature=temperature,
|
| 927 |
+
top_p=top_p,
|
| 928 |
+
top_k=top_k,
|
| 929 |
+
repetition_penalty=repetition_penalty,
|
| 930 |
+
max_new_tokens=max_new_tokens,
|
| 931 |
+
temps=chunk_temps,
|
| 932 |
+
debug=debug,
|
| 933 |
+
scheduler=scheduler,
|
| 934 |
+
group_size=0,
|
| 935 |
+
vision_features=chunk_vision_features,
|
| 936 |
+
_stats=_stats,
|
| 937 |
+
))
|
| 938 |
+
if top_level_stats:
|
| 939 |
+
_set_last_hybrid_stats(_stats)
|
| 940 |
+
return outs
|
| 941 |
+
|
| 942 |
+
use_cached_tokenize = (
|
| 943 |
+
vision_features is not None
|
| 944 |
+
and os.environ.get("LA_FLASH_CACHE_TOKENIZE", "1") != "0"
|
| 945 |
+
)
|
| 946 |
+
if use_cached_tokenize:
|
| 947 |
+
try:
|
| 948 |
+
prompt_ids = [
|
| 949 |
+
_tokenize_cached_image(q, int(v.shape[0]), im=im)
|
| 950 |
+
for (im, q), v in zip(pairs, vision_features)
|
| 951 |
+
]
|
| 952 |
+
except Exception as exc:
|
| 953 |
+
if os.environ.get("LA_FLASH_CACHE_TOKENIZE_STRICT", "0") == "1":
|
| 954 |
+
raise
|
| 955 |
+
if debug:
|
| 956 |
+
print(f"LA Flash cached tokenize fallback: {exc}", flush=True)
|
| 957 |
+
prompt_ids = [_tokenize(im, q) for im, q in pairs]
|
| 958 |
+
else:
|
| 959 |
+
prompt_ids = [_tokenize(im, q) for im, q in pairs]
|
| 960 |
+
vit_list = (
|
| 961 |
+
list(vision_features)
|
| 962 |
+
if vision_features is not None
|
| 963 |
+
else _encode_images([im for im, _ in pairs])
|
| 964 |
+
)
|
| 965 |
+
lengths = [int(p.numel()) for p in prompt_ids]
|
| 966 |
+
bsz = len(pairs)
|
| 967 |
+
_record_group_stats(_stats, bsz)
|
| 968 |
+
|
| 969 |
+
_set_llm_mode(model, requested_attn)
|
| 970 |
+
|
| 971 |
+
modes = ["mtp"] * bsz
|
| 972 |
+
finished = [False] * bsz
|
| 973 |
+
gen_ids = [[] for _ in range(bsz)]
|
| 974 |
+
full_ids = [list(ids.detach().cpu().tolist()) for ids in prompt_ids]
|
| 975 |
+
kv_rows, cached_lens = _prefill_prompt_kv_rows(
|
| 976 |
+
model, prompt_ids, vit_list, img_tok, pad, dev, prefill_mode, debug=debug, stats=_stats)
|
| 977 |
+
total_limits = [lengths[b] + max_new_tokens for b in range(bsz)]
|
| 978 |
+
|
| 979 |
+
row_temps = [float(temperature or 0.0)] * bsz if temps is None else [float(t or 0.0) for t in temps]
|
| 980 |
+
|
| 981 |
+
def run_ar(ar_rows, step_idx):
|
| 982 |
+
row_groups = _split_rows_by_kv_budget(ar_rows, kv_rows)
|
| 983 |
+
_record_kv_bucket_stats(_stats, row_groups, kv_rows)
|
| 984 |
+
for row_group in row_groups:
|
| 985 |
+
_step_stock_ar_rows(
|
| 986 |
+
model, san, tids, prompt_ids, kv_rows, row_group,
|
| 987 |
+
cached_lens, full_ids, gen_ids, modes, finished, total_limits,
|
| 988 |
+
pad, img_tok, row_temps, temperature, top_p, top_k,
|
| 989 |
+
repetition_penalty, dev, tok, debug, step_idx, use_magi, _stats,
|
| 990 |
+
)
|
| 991 |
+
|
| 992 |
+
def run_mtp(mtp_rows, step_idx):
|
| 993 |
+
if any(cached_lens[r] == 0 for r in mtp_rows) and any(cached_lens[r] > 0 for r in mtp_rows):
|
| 994 |
+
first_rows = [r for r in mtp_rows if cached_lens[r] == 0]
|
| 995 |
+
cached_rows = [r for r in mtp_rows if cached_lens[r] > 0]
|
| 996 |
+
if first_rows:
|
| 997 |
+
run_mtp(first_rows, step_idx)
|
| 998 |
+
if cached_rows:
|
| 999 |
+
run_mtp(cached_rows, step_idx)
|
| 1000 |
+
return
|
| 1001 |
+
row_groups = _split_rows_by_kv_budget(mtp_rows, kv_rows)
|
| 1002 |
+
_record_kv_bucket_stats(_stats, row_groups, kv_rows)
|
| 1003 |
+
if len(row_groups) > 1:
|
| 1004 |
+
for row_group in row_groups:
|
| 1005 |
+
run_mtp(row_group, step_idx)
|
| 1006 |
+
return
|
| 1007 |
+
_step_stock_mtp_rows(
|
| 1008 |
+
model, san, hpat, tids, prompt_ids, kv_rows, mtp_rows,
|
| 1009 |
+
cached_lens, full_ids, gen_ids, modes, finished, total_limits,
|
| 1010 |
+
vit_list, pad, mask_tok, img_tok, row_temps, top_p, top_k,
|
| 1011 |
+
repetition_penalty, dev, tok, debug, step_idx, use_magi, _stats,
|
| 1012 |
+
)
|
| 1013 |
+
|
| 1014 |
+
def live_rows(mode):
|
| 1015 |
+
return [b for b in range(bsz) if not finished[b] and modes[b] == mode]
|
| 1016 |
+
|
| 1017 |
+
step = 0
|
| 1018 |
+
hold_steps = 0
|
| 1019 |
+
while not all(finished) and step <= max_new_tokens:
|
| 1020 |
+
step += 1
|
| 1021 |
+
if _stats is not None:
|
| 1022 |
+
_stats["decode_loops"] += 1
|
| 1023 |
+
if scheduler == "hold_ar" and hold_max_steps > 0:
|
| 1024 |
+
ar_rows = live_rows("ar")
|
| 1025 |
+
mtp_rows = live_rows("mtp")
|
| 1026 |
+
if ar_rows and mtp_rows and _stats is not None:
|
| 1027 |
+
_stats["mixed_mode_cycles"] += 1
|
| 1028 |
+
if ar_rows and (hold_steps < hold_max_steps or not mtp_rows):
|
| 1029 |
+
if mtp_rows and _stats is not None:
|
| 1030 |
+
_stats["hold_ar_steps"] += 1
|
| 1031 |
+
_stats["hold_ar_held_mtp_rows"] += len(mtp_rows)
|
| 1032 |
+
run_ar(ar_rows, step)
|
| 1033 |
+
hold_steps += 1
|
| 1034 |
+
continue
|
| 1035 |
+
if mtp_rows:
|
| 1036 |
+
if ar_rows and _stats is not None:
|
| 1037 |
+
_stats["hold_ar_limit_mtp_forwards"] += 1
|
| 1038 |
+
run_mtp(mtp_rows, step)
|
| 1039 |
+
hold_steps = 0
|
| 1040 |
+
continue
|
| 1041 |
+
|
| 1042 |
+
if scheduler in {"ar_first", "pipeline", "adaptive"}:
|
| 1043 |
+
ar_rows_at_loop_start = live_rows("ar")
|
| 1044 |
+
mtp_rows_at_loop_start = live_rows("mtp")
|
| 1045 |
+
mixed = bool(ar_rows_at_loop_start and mtp_rows_at_loop_start)
|
| 1046 |
+
if mixed and _stats is not None:
|
| 1047 |
+
_stats["mixed_mode_cycles"] += 1
|
| 1048 |
+
|
| 1049 |
+
if scheduler == "adaptive" and mixed and hold_max_steps > 0:
|
| 1050 |
+
should_hold = len(mtp_rows_at_loop_start) <= adaptive_hold_mtp_max
|
| 1051 |
+
if should_hold and hold_steps < hold_max_steps:
|
| 1052 |
+
if _stats is not None:
|
| 1053 |
+
_stats["adaptive_hold_cycles"] += 1
|
| 1054 |
+
_stats["hold_ar_steps"] += 1
|
| 1055 |
+
_stats["hold_ar_held_mtp_rows"] += len(mtp_rows_at_loop_start)
|
| 1056 |
+
run_ar(ar_rows_at_loop_start, step)
|
| 1057 |
+
hold_steps += 1
|
| 1058 |
+
continue
|
| 1059 |
+
|
| 1060 |
+
if ar_rows_at_loop_start:
|
| 1061 |
+
if mixed and _stats is not None:
|
| 1062 |
+
if scheduler == "adaptive":
|
| 1063 |
+
_stats["adaptive_ar_first_cycles"] += 1
|
| 1064 |
+
else:
|
| 1065 |
+
_stats["ar_first_cycles"] += 1
|
| 1066 |
+
run_ar(ar_rows_at_loop_start, step)
|
| 1067 |
+
|
| 1068 |
+
mtp_rows = live_rows("mtp")
|
| 1069 |
+
if mtp_rows:
|
| 1070 |
+
run_mtp(mtp_rows, step)
|
| 1071 |
+
hold_steps = 0
|
| 1072 |
+
|
| 1073 |
+
if scheduler == "pipeline" and mtp_rows:
|
| 1074 |
+
old_ar = set(ar_rows_at_loop_start)
|
| 1075 |
+
new_ar_rows = [b for b in live_rows("ar") if b not in old_ar]
|
| 1076 |
+
if new_ar_rows:
|
| 1077 |
+
if _stats is not None:
|
| 1078 |
+
_stats["pipeline_ar_after_mtp_cycles"] += 1
|
| 1079 |
+
run_ar(new_ar_rows, step)
|
| 1080 |
+
continue
|
| 1081 |
+
|
| 1082 |
+
mtp_rows = live_rows("mtp")
|
| 1083 |
+
ar_rows_at_loop_start = live_rows("ar")
|
| 1084 |
+
if mtp_rows and ar_rows_at_loop_start and _stats is not None:
|
| 1085 |
+
_stats["mixed_mode_cycles"] += 1
|
| 1086 |
+
if mtp_rows:
|
| 1087 |
+
run_mtp(mtp_rows, step)
|
| 1088 |
+
|
| 1089 |
+
ar_rows = [b for b in range(bsz) if not finished[b] and modes[b] == "ar"]
|
| 1090 |
+
if mtp_rows and ar_rows and _stats is not None:
|
| 1091 |
+
_stats["eager_mtp_then_ar_cycles"] += 1
|
| 1092 |
+
if ar_rows:
|
| 1093 |
+
run_ar(ar_rows, step)
|
| 1094 |
+
|
| 1095 |
+
outs = [
|
| 1096 |
+
tok.decode(torch.tensor(gen_ids[b], dtype=torch.long, device=dev),
|
| 1097 |
+
skip_special_tokens=False) if gen_ids[b] else ""
|
| 1098 |
+
for b in range(bsz)
|
| 1099 |
+
]
|
| 1100 |
+
if top_level_stats:
|
| 1101 |
+
if os.environ.get("LA_FLASH_PLAN_STATS", "0") == "1":
|
| 1102 |
+
_stats["sparse_plan_stats"] = copy.deepcopy(
|
| 1103 |
+
getattr(model, "_la_flash_sparse_plan_stats", None) or {}
|
| 1104 |
+
)
|
| 1105 |
+
_set_last_hybrid_stats(_stats)
|
| 1106 |
+
return outs
|
| 1107 |
+
|
| 1108 |
+
|
| 1109 |
+
@torch.no_grad()
|
| 1110 |
+
def _step_stock_mtp_rows(model, san, hpat, tids, prompt_ids, kv_rows, rows,
|
| 1111 |
+
cached_lens, full_ids, gen_ids, modes, finished, total_limits,
|
| 1112 |
+
vit_list, pad, mask_tok, img_tok, row_temps, top_p, top_k,
|
| 1113 |
+
repetition_penalty, dev, tok, debug, step_idx, use_magi, stats=None):
|
| 1114 |
+
kv, kvalid, old_lens, kmax = _pack_stock_kv_rows(kv_rows, rows, dev)
|
| 1115 |
+
uncached_lens = [len(full_ids[r]) - cached_lens[r] for r in rows]
|
| 1116 |
+
umax = max(uncached_lens)
|
| 1117 |
+
seq_len = umax + N_FUTURE
|
| 1118 |
+
_record_forward_stats(stats, "mtp", rows, seq_len, uncached_lens)
|
| 1119 |
+
|
| 1120 |
+
suf_ids = torch.full((len(rows), seq_len), pad, dtype=torch.long, device=dev)
|
| 1121 |
+
suf_pos = torch.ones((len(rows), seq_len), dtype=torch.long, device=dev)
|
| 1122 |
+
q_valid = torch.zeros((len(rows), seq_len), dtype=torch.long, device=dev)
|
| 1123 |
+
|
| 1124 |
+
for i, r in enumerate(rows):
|
| 1125 |
+
uncached = full_ids[r][cached_lens[r] :]
|
| 1126 |
+
left = umax - len(uncached)
|
| 1127 |
+
if uncached:
|
| 1128 |
+
suf_ids[i, left : left + len(uncached)] = torch.tensor(uncached, dtype=torch.long, device=dev)
|
| 1129 |
+
suf_pos[i, left : left + len(uncached)] = torch.arange(
|
| 1130 |
+
cached_lens[r], len(full_ids[r]), dtype=torch.long, device=dev)
|
| 1131 |
+
q_valid[i, left : left + len(uncached)] = 1
|
| 1132 |
+
|
| 1133 |
+
rep = full_ids[r][-1]
|
| 1134 |
+
cur_len = len(full_ids[r])
|
| 1135 |
+
suf_ids[i, umax] = rep
|
| 1136 |
+
suf_pos[i, umax] = cur_len - 1
|
| 1137 |
+
q_valid[i, umax] = 1
|
| 1138 |
+
for j in range(1, N_FUTURE):
|
| 1139 |
+
suf_ids[i, umax + j] = mask_tok
|
| 1140 |
+
suf_pos[i, umax + j] = cur_len + (j - 1)
|
| 1141 |
+
q_valid[i, umax + j] = 1
|
| 1142 |
+
|
| 1143 |
+
full_mask = torch.cat([kvalid, q_valid], dim=1)
|
| 1144 |
+
|
| 1145 |
+
if debug:
|
| 1146 |
+
forward_mask, fallback_rows = _forward_attention_mask(
|
| 1147 |
+
model, suf_ids, full_mask, kmax, mtp_window=True, range_plan=True)
|
| 1148 |
+
_print_debug_forward(
|
| 1149 |
+
f"MTP step={step_idx}",
|
| 1150 |
+
model,
|
| 1151 |
+
tok,
|
| 1152 |
+
suf_ids,
|
| 1153 |
+
full_mask,
|
| 1154 |
+
suf_pos,
|
| 1155 |
+
past_len=kmax,
|
| 1156 |
+
mtp_window=True,
|
| 1157 |
+
extra={
|
| 1158 |
+
"global_rows": rows,
|
| 1159 |
+
"old_kv_lens": old_lens,
|
| 1160 |
+
"cached_lens": [cached_lens[r] for r in rows],
|
| 1161 |
+
"full_lens": [len(full_ids[r]) for r in rows],
|
| 1162 |
+
"uncached_lens": uncached_lens,
|
| 1163 |
+
"forward_attention_mask": _mask_desc(forward_mask),
|
| 1164 |
+
"safe_sdpa_fallback_query_rows": fallback_rows,
|
| 1165 |
+
},
|
| 1166 |
+
attention_impl="magi" if use_magi else ATTN_MODE,
|
| 1167 |
+
)
|
| 1168 |
+
else:
|
| 1169 |
+
forward_mask, _ = _forward_attention_mask(
|
| 1170 |
+
model, suf_ids, full_mask, kmax, mtp_window=True, range_plan=True)
|
| 1171 |
+
first_rows = [r for r in rows if cached_lens[r] == 0]
|
| 1172 |
+
visual_features = None
|
| 1173 |
+
if first_rows:
|
| 1174 |
+
if first_rows != rows:
|
| 1175 |
+
raise RuntimeError("mixed first/non-first MTP rows are not supported")
|
| 1176 |
+
visual_features = torch.cat([vit_list[r] for r in rows], dim=0)
|
| 1177 |
+
assert int((suf_ids == img_tok).sum().item()) == visual_features.shape[0], \
|
| 1178 |
+
"image-token count != supplied visual_features rows"
|
| 1179 |
+
out = language_model_forward(
|
| 1180 |
+
model, input_ids=suf_ids, attention_mask=forward_mask,
|
| 1181 |
+
position_ids=suf_pos, past_key_values=kv, use_cache=True,
|
| 1182 |
+
visual_features=visual_features,
|
| 1183 |
+
image_token_index=img_tok if visual_features is not None else None,
|
| 1184 |
+
logits_slice=slice(-N_FUTURE, None))
|
| 1185 |
+
|
| 1186 |
+
for i, r in enumerate(rows):
|
| 1187 |
+
kv_rows[r] = _unpack_stock_after_forward(
|
| 1188 |
+
out.past_key_values, i, old_lens[i], uncached_lens[i], kmax, umax)
|
| 1189 |
+
cached_lens[r] = len(full_ids[r])
|
| 1190 |
+
|
| 1191 |
+
wlogits = out.logits[:, -N_FUTURE:, :]
|
| 1192 |
+
local_prompts = [prompt_ids[r] for r in rows]
|
| 1193 |
+
local_gen = [gen_ids[r] for r in rows]
|
| 1194 |
+
gen_pad = _pad_generated(local_prompts, local_gen, img_tok, dev)
|
| 1195 |
+
per_row_temp = torch.tensor([row_temps[r] for r in rows], dtype=torch.float32, device=dev)
|
| 1196 |
+
|
| 1197 |
+
if BATCH_SAN:
|
| 1198 |
+
x0_all, boxes_all = sample_tokens_batched(
|
| 1199 |
+
wlogits, gen_pad, tids, per_row_temp,
|
| 1200 |
+
repetition_penalty=repetition_penalty, top_p=top_p, top_k=top_k,
|
| 1201 |
+
keep_k_avg=4, generation_mode="hybrid")
|
| 1202 |
+
|
| 1203 |
+
for i, r in enumerate(rows):
|
| 1204 |
+
if finished[r]:
|
| 1205 |
+
continue
|
| 1206 |
+
if BATCH_SAN:
|
| 1207 |
+
x0b, boxb = x0_all[i], boxes_all[i]
|
| 1208 |
+
else:
|
| 1209 |
+
gk = _mk_generate_kwargs(row_temps[r], top_p, top_k, repetition_penalty)
|
| 1210 |
+
_, _, x0, box_avg = san(wlogits[i : i + 1], gen_pad[i : i + 1], tids, keep_k=5, **gk)
|
| 1211 |
+
x0b, boxb = x0[0], box_avg[0]
|
| 1212 |
+
nt = x0b if bool((boxb == 0).all()) else boxb
|
| 1213 |
+
op = hpat(nt, tids, "hybrid")
|
| 1214 |
+
|
| 1215 |
+
toks = [int(t) for t in op["tokens"]]
|
| 1216 |
+
for t in toks:
|
| 1217 |
+
gen_ids[r].append(t)
|
| 1218 |
+
full_ids[r].append(t)
|
| 1219 |
+
|
| 1220 |
+
if op["type"] == "im_end":
|
| 1221 |
+
finished[r] = True
|
| 1222 |
+
elif op["type"] == "error_box":
|
| 1223 |
+
modes[r] = "ar"
|
| 1224 |
+
if len(full_ids[r]) >= total_limits[r]:
|
| 1225 |
+
finished[r] = True
|
| 1226 |
+
|
| 1227 |
+
|
| 1228 |
+
@torch.no_grad()
|
| 1229 |
+
def _step_stock_ar_rows(model, san, tids, prompt_ids, kv_rows, rows,
|
| 1230 |
+
cached_lens, full_ids, gen_ids, modes, finished, total_limits,
|
| 1231 |
+
pad, img_tok, row_temps, temperature, top_p, top_k,
|
| 1232 |
+
repetition_penalty, dev, tok, debug, step_idx, use_magi, stats=None):
|
| 1233 |
+
kv, kvalid, old_lens, kmax = _pack_stock_kv_rows(kv_rows, rows, dev)
|
| 1234 |
+
uncached_lens = [len(full_ids[r]) - cached_lens[r] for r in rows]
|
| 1235 |
+
if any(n <= 0 for n in uncached_lens):
|
| 1236 |
+
raise RuntimeError(f"AR rows have no uncached tokens: {rows}")
|
| 1237 |
+
umax = max(uncached_lens)
|
| 1238 |
+
_record_forward_stats(stats, "ar", rows, umax, uncached_lens)
|
| 1239 |
+
|
| 1240 |
+
suf_ids = torch.full((len(rows), umax), pad, dtype=torch.long, device=dev)
|
| 1241 |
+
suf_pos = torch.ones((len(rows), umax), dtype=torch.long, device=dev)
|
| 1242 |
+
q_valid = torch.zeros((len(rows), umax), dtype=torch.long, device=dev)
|
| 1243 |
+
|
| 1244 |
+
for i, r in enumerate(rows):
|
| 1245 |
+
uncached = full_ids[r][cached_lens[r] :]
|
| 1246 |
+
left = umax - len(uncached)
|
| 1247 |
+
suf_ids[i, left:] = torch.tensor(uncached, dtype=torch.long, device=dev)
|
| 1248 |
+
suf_pos[i, left:] = torch.arange(cached_lens[r], len(full_ids[r]), dtype=torch.long, device=dev)
|
| 1249 |
+
q_valid[i, left:] = 1
|
| 1250 |
+
|
| 1251 |
+
full_mask = torch.cat([kvalid, q_valid], dim=1)
|
| 1252 |
+
if debug:
|
| 1253 |
+
forward_mask, fallback_rows = _forward_attention_mask(
|
| 1254 |
+
model, suf_ids, full_mask, kmax, mtp_window=False, range_plan=True)
|
| 1255 |
+
_print_debug_forward(
|
| 1256 |
+
f"AR step={step_idx}",
|
| 1257 |
+
model,
|
| 1258 |
+
tok,
|
| 1259 |
+
suf_ids,
|
| 1260 |
+
full_mask,
|
| 1261 |
+
suf_pos,
|
| 1262 |
+
past_len=kmax,
|
| 1263 |
+
mtp_window=False,
|
| 1264 |
+
extra={
|
| 1265 |
+
"global_rows": rows,
|
| 1266 |
+
"old_kv_lens": old_lens,
|
| 1267 |
+
"cached_lens": [cached_lens[r] for r in rows],
|
| 1268 |
+
"full_lens": [len(full_ids[r]) for r in rows],
|
| 1269 |
+
"uncached_lens": uncached_lens,
|
| 1270 |
+
"forward_attention_mask": _mask_desc(forward_mask),
|
| 1271 |
+
"safe_sdpa_fallback_query_rows": fallback_rows,
|
| 1272 |
+
},
|
| 1273 |
+
attention_impl="magi" if use_magi else ATTN_MODE,
|
| 1274 |
+
)
|
| 1275 |
+
else:
|
| 1276 |
+
forward_mask, _ = _forward_attention_mask(
|
| 1277 |
+
model, suf_ids, full_mask, kmax, mtp_window=False, range_plan=True)
|
| 1278 |
+
|
| 1279 |
+
out = language_model_forward(
|
| 1280 |
+
model, input_ids=suf_ids, attention_mask=forward_mask,
|
| 1281 |
+
position_ids=suf_pos, past_key_values=kv, use_cache=True,
|
| 1282 |
+
logits_slice=slice(-1, None))
|
| 1283 |
+
|
| 1284 |
+
for i, r in enumerate(rows):
|
| 1285 |
+
kv_rows[r] = _unpack_stock_after_forward(
|
| 1286 |
+
out.past_key_values, i, old_lens[i], uncached_lens[i], kmax, umax)
|
| 1287 |
+
cached_lens[r] = len(full_ids[r])
|
| 1288 |
+
|
| 1289 |
+
if AR_BATCH_SAN:
|
| 1290 |
+
local_prompts = [prompt_ids[r] for r in rows]
|
| 1291 |
+
local_gen = [gen_ids[r] for r in rows]
|
| 1292 |
+
gen_pad = _pad_generated(local_prompts, local_gen, img_tok, dev)
|
| 1293 |
+
per_row_temp = torch.tensor([row_temps[r] for r in rows], dtype=torch.float32, device=dev)
|
| 1294 |
+
x0_all = sample_next_tokens_batched(
|
| 1295 |
+
out.logits[:, -1:, :],
|
| 1296 |
+
gen_pad,
|
| 1297 |
+
per_row_temp,
|
| 1298 |
+
repetition_penalty=repetition_penalty,
|
| 1299 |
+
top_p=top_p,
|
| 1300 |
+
top_k=top_k,
|
| 1301 |
+
)
|
| 1302 |
+
|
| 1303 |
+
for i, r in enumerate(rows):
|
| 1304 |
+
if AR_BATCH_SAN:
|
| 1305 |
+
token_val = int(x0_all[i, 0].item())
|
| 1306 |
+
else:
|
| 1307 |
+
logits = out.logits[i : i + 1, -1:, :]
|
| 1308 |
+
gen_pad = _pad_generated([prompt_ids[r]], [gen_ids[r]], img_tok, dev)
|
| 1309 |
+
gk = _mk_generate_kwargs(temperature, top_p, top_k, repetition_penalty, row_temp=row_temps[r])
|
| 1310 |
+
_, _, x0, _ = san(logits, gen_pad, tids, **gk)
|
| 1311 |
+
token_val = int(x0[0, 0].item())
|
| 1312 |
+
out_type = _classify_ar_token(token_val, tids)
|
| 1313 |
+
|
| 1314 |
+
gen_ids[r].append(token_val)
|
| 1315 |
+
full_ids[r].append(token_val)
|
| 1316 |
+
|
| 1317 |
+
if out_type == "im_end":
|
| 1318 |
+
finished[r] = True
|
| 1319 |
+
elif out_type == "box_end_ar":
|
| 1320 |
+
modes[r] = "mtp"
|
| 1321 |
+
|
| 1322 |
+
if len(full_ids[r]) >= total_limits[r]:
|
| 1323 |
+
finished[r] = True
|
| 1324 |
+
|
| 1325 |
+
|
| 1326 |
+
def generate_batch_grouped_hybrid(groups, temperature=README_TEMPERATURE, top_p=README_TOP_P,
|
| 1327 |
+
top_k=None, repetition_penalty=README_REPETITION_PENALTY,
|
| 1328 |
+
max_new_tokens=README_MAX_NEW_TOKENS, temps=None,
|
| 1329 |
+
debug=None, scheduler=None, group_size=None,
|
| 1330 |
+
vision_features=None):
|
| 1331 |
+
"""Hybrid grouped API shape.
|
| 1332 |
+
|
| 1333 |
+
This preserves grouped return shape, but intentionally uses the generic
|
| 1334 |
+
hybrid decoder rather than the fast engine's shared-prefix optimization.
|
| 1335 |
+
"""
|
| 1336 |
+
flat = []
|
| 1337 |
+
flat_vision_features = [] if vision_features is not None else None
|
| 1338 |
+
counts = []
|
| 1339 |
+
for group_idx, (im, queries) in enumerate(groups):
|
| 1340 |
+
counts.append(len(queries))
|
| 1341 |
+
flat.extend((im, q) for q in queries)
|
| 1342 |
+
if flat_vision_features is not None:
|
| 1343 |
+
flat_vision_features.extend([vision_features[group_idx]] * len(queries))
|
| 1344 |
+
|
| 1345 |
+
outs = generate_batch_hybrid(
|
| 1346 |
+
flat, temperature=temperature, top_p=top_p, top_k=top_k,
|
| 1347 |
+
repetition_penalty=repetition_penalty, max_new_tokens=max_new_tokens,
|
| 1348 |
+
temps=temps, debug=debug, scheduler=scheduler, group_size=group_size,
|
| 1349 |
+
vision_features=flat_vision_features)
|
| 1350 |
+
res, offset = [], 0
|
| 1351 |
+
for n in counts:
|
| 1352 |
+
res.append(outs[offset : offset + n])
|
| 1353 |
+
offset += n
|
| 1354 |
+
return res
|
| 1355 |
+
|
| 1356 |
+
|
| 1357 |
+
__all__ = ["generate_batch_hybrid", "generate_batch_grouped_hybrid", "get_last_hybrid_stats"]
|
batch_utils/hybrid_runtime.py
ADDED
|
@@ -0,0 +1,1842 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Internal runtime support for the LocateAnything-3B hybrid batch decoder.
|
| 2 |
+
|
| 3 |
+
This file keeps only the model-loading, tokenization, image-encoding, stock
|
| 4 |
+
processor, and sample-token helpers that ``engine_hybrid.py`` needs.
|
| 5 |
+
|
| 6 |
+
Important env knobs:
|
| 7 |
+
LA_FLASH_MODEL HF repo id / local path of the model (default nvidia/LocateAnything-3B)
|
| 8 |
+
HF_HUB_OFFLINE=1 read the local HF cache only (no network); unset -> download on first use
|
| 9 |
+
LA_FLASH_ATTN sdpa, eager, magi, or la_flash; la_flash uses FlashAttention sparse ranges
|
| 10 |
+
LA_FLASH_STRICT_ATTN 1 -> fail if the requested backend is unavailable;
|
| 11 |
+
default 0 falls back to sdpa
|
| 12 |
+
LA_FLASH_VISION_ATTN auto, flash_attention_2, sdpa, or eager (default auto)
|
| 13 |
+
LA_FLASH_HYBRID_PREFILL shared, none, per_row, or batch prompt KV prefill (default shared)
|
| 14 |
+
MTP_BATCH_VISION 0 -> per-image vision encode (default 1: batched when flash is present)
|
| 15 |
+
LA_FLASH_VISION_ENCODE_BATCH_SIZE
|
| 16 |
+
max images per MoonViT encode micro-batch (default 8; <=0 disables limit)
|
| 17 |
+
MTP_BATCH_SAN 0 -> per-row logits/sample pipeline (default 1: batched over [B,6,V])
|
| 18 |
+
AR_BATCH_SAN 0 -> per-row AR sample pipeline (default 1: batched over [B,1,V])
|
| 19 |
+
"""
|
| 20 |
+
import inspect
|
| 21 |
+
import os, warnings, importlib, torch
|
| 22 |
+
from types import SimpleNamespace
|
| 23 |
+
import numpy as np
|
| 24 |
+
from transformers import AutoModel, AutoTokenizer, AutoProcessor
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# By default let transformers fetch the model on first use; set HF_HUB_OFFLINE=1 yourself
|
| 28 |
+
# to read the local HF cache only (e.g. air-gapped / already-downloaded runs).
|
| 29 |
+
MODEL = os.environ.get("LA_FLASH_MODEL", "nvidia/LocateAnything-3B")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
LLM_ATTN_MODES = ("sdpa", "eager", "magi", "la_flash")
|
| 33 |
+
VISION_ATTN_MODES = ("auto", "flash_attention_2", "sdpa", "eager")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _normalize_attn_mode(value):
|
| 37 |
+
mode = (value or "sdpa").strip().lower().replace("-", "_")
|
| 38 |
+
aliases = {
|
| 39 |
+
"": "sdpa",
|
| 40 |
+
"manual": "eager",
|
| 41 |
+
"torch": "eager",
|
| 42 |
+
"torch_eager": "eager",
|
| 43 |
+
"torch_sdpa": "sdpa",
|
| 44 |
+
"scaled_dot_product_attention": "sdpa",
|
| 45 |
+
"flash": "la_flash",
|
| 46 |
+
"la_flash": "la_flash",
|
| 47 |
+
"kernel": "la_flash",
|
| 48 |
+
"cuda": "la_flash",
|
| 49 |
+
"range": "la_flash",
|
| 50 |
+
"range_attention": "la_flash",
|
| 51 |
+
"flex_flash": "magi",
|
| 52 |
+
"flex_flash_attention": "magi",
|
| 53 |
+
"flex_flash_attn": "magi",
|
| 54 |
+
}
|
| 55 |
+
mode = aliases.get(mode, mode)
|
| 56 |
+
if mode not in LLM_ATTN_MODES:
|
| 57 |
+
raise ValueError(
|
| 58 |
+
f"LA_FLASH_ATTN must be one of {', '.join(LLM_ATTN_MODES)}; got {value!r}"
|
| 59 |
+
)
|
| 60 |
+
return mode
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _normalize_vision_attn_mode(value):
|
| 64 |
+
mode = (value or "auto").strip().lower().replace("-", "_")
|
| 65 |
+
aliases = {
|
| 66 |
+
"": "auto",
|
| 67 |
+
"flash": "flash_attention_2",
|
| 68 |
+
"flash_attention2": "flash_attention_2",
|
| 69 |
+
"fa2": "flash_attention_2",
|
| 70 |
+
"manual": "eager",
|
| 71 |
+
}
|
| 72 |
+
mode = aliases.get(mode, mode)
|
| 73 |
+
if mode not in VISION_ATTN_MODES:
|
| 74 |
+
raise ValueError(
|
| 75 |
+
f"LA_FLASH_VISION_ATTN must be one of {', '.join(VISION_ATTN_MODES)}; got {value!r}"
|
| 76 |
+
)
|
| 77 |
+
return mode
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
ATTN_MODE = _normalize_attn_mode(os.environ.get("LA_FLASH_ATTN", "sdpa"))
|
| 81 |
+
REMOTE_ATTN_MODE = "sdpa" if ATTN_MODE in {"la_flash", "magi"} else ATTN_MODE
|
| 82 |
+
VISION_ATTN_MODE = _normalize_vision_attn_mode(os.environ.get("LA_FLASH_VISION_ATTN", "auto"))
|
| 83 |
+
MAX_DIM = 1024
|
| 84 |
+
DEV, DT = "cuda", torch.bfloat16
|
| 85 |
+
N_FUTURE = 6 # = config.block_size (MTP window)
|
| 86 |
+
_PROMPT = "Locate all the instances that matches the following description: "
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _env_flag(name, default=False):
|
| 90 |
+
val = os.environ.get(name)
|
| 91 |
+
if val is None:
|
| 92 |
+
return default
|
| 93 |
+
return val.strip().lower() not in {"0", "false", "no", "off"}
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def _env_int(name):
|
| 97 |
+
val = os.environ.get(name)
|
| 98 |
+
if val is None or val.strip() == "":
|
| 99 |
+
return None
|
| 100 |
+
return int(val)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def _strict_attn():
|
| 104 |
+
return _env_flag("LA_FLASH_STRICT_ATTN", False)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _fallback_to_sdpa(model, requested, reason):
|
| 108 |
+
if requested == "sdpa":
|
| 109 |
+
raise RuntimeError(f"LA_FLASH_ATTN=sdpa failed: {reason}") from reason
|
| 110 |
+
message = f"LA_FLASH_ATTN={requested} is unavailable; falling back to sdpa. Reason: {reason}"
|
| 111 |
+
if _strict_attn():
|
| 112 |
+
raise RuntimeError(message) from reason
|
| 113 |
+
warnings.warn(message)
|
| 114 |
+
_set_llm_mode(model, "sdpa")
|
| 115 |
+
model._la_flash_requested_attn_original = requested
|
| 116 |
+
model._la_flash_attn_fallback_reason = str(reason)
|
| 117 |
+
return "sdpa"
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# Optional compile for the shared Qwen2 core. This is off by default because the
|
| 121 |
+
# hybrid scheduler already varies query/cache shapes and first-call compile cost is high.
|
| 122 |
+
MTP_COMPILE = os.environ.get("MTP_COMPILE", "0") == "1"
|
| 123 |
+
|
| 124 |
+
# Batch the MoonViT vision encode across a micro-batch's images: pack N images into ONE
|
| 125 |
+
# extract_feature. With flash present, MoonViT's varlen cu_seqlens path is block-diagonal per
|
| 126 |
+
# image and equivalent to per-image encode.
|
| 127 |
+
# Without flash, sdpa builds a dense [1,S,S] mask -> O(S^2) N^2 -> per-image fallback (auto, see
|
| 128 |
+
# _vision_is_flash). Default ON; set MTP_BATCH_VISION=0 to force per-image.
|
| 129 |
+
BATCH_VISION = os.environ.get("MTP_BATCH_VISION", "1") == "1"
|
| 130 |
+
_vision_encode_batch_size = _env_int("LA_FLASH_VISION_ENCODE_BATCH_SIZE")
|
| 131 |
+
VISION_ENCODE_BATCH_SIZE = 8 if _vision_encode_batch_size is None else max(0, _vision_encode_batch_size)
|
| 132 |
+
|
| 133 |
+
# Batch the per-row box-decode (sample_tokens): run the row-independent logits pipeline
|
| 134 |
+
# (rep-penalty / per-row temperature / top_p / top_k / softmax / sample) ONCE over the whole
|
| 135 |
+
# [B,6,V] step instead of B times on [1,6,V]; only the variable-length box assembly stays per-row.
|
| 136 |
+
# Greedy is BIT-IDENTICAL to the per-row san (argmax, no RNG). Default ON; MTP_BATCH_SAN=0 -> per-row.
|
| 137 |
+
BATCH_SAN = os.environ.get("MTP_BATCH_SAN", "1") == "1"
|
| 138 |
+
|
| 139 |
+
# Batch the AR repair sampler over [B,1,V]. This shares the exact filtering
|
| 140 |
+
# helpers with MTP batching but skips box/ref decoding, so it only replaces the
|
| 141 |
+
# repeated stock one-token sample calls. Sampling itself stays row-ordered by
|
| 142 |
+
# default to preserve the stock RNG consumption pattern for AR repair.
|
| 143 |
+
AR_BATCH_SAN = os.environ.get("AR_BATCH_SAN", "1") == "1"
|
| 144 |
+
|
| 145 |
+
_tok = _proc = _model = None
|
| 146 |
+
|
| 147 |
+
def _magi_diag():
|
| 148 |
+
lines = []
|
| 149 |
+
try:
|
| 150 |
+
import magi_attention
|
| 151 |
+
lines.append(f"magi_attention: OK file={getattr(magi_attention, '__file__', None)}")
|
| 152 |
+
lines.append(f"magi_attention.__version__={getattr(magi_attention, '__version__', '<missing>')}")
|
| 153 |
+
except Exception as e:
|
| 154 |
+
lines.append(f"magi_attention: FAIL {type(e).__name__}: {e}")
|
| 155 |
+
return "\n".join(lines)
|
| 156 |
+
try:
|
| 157 |
+
from magi_attention.functional.flex_flash_attn import flex_flash_attn_func
|
| 158 |
+
lines.append(f"magi_attention.functional.flex_flash_attn: OK func={flex_flash_attn_func}")
|
| 159 |
+
except Exception as e:
|
| 160 |
+
lines.append(f"magi_attention.functional.flex_flash_attn: FAIL {type(e).__name__}: {e}")
|
| 161 |
+
return "\n".join(lines)
|
| 162 |
+
|
| 163 |
+
def _remote_magi_diag(model=None):
|
| 164 |
+
lines = []
|
| 165 |
+
try:
|
| 166 |
+
if model is not None:
|
| 167 |
+
mod = importlib.import_module(type(model.language_model.model).__module__)
|
| 168 |
+
else:
|
| 169 |
+
# Best effort: if the dynamic module is not imported yet this may fail;
|
| 170 |
+
# the post-load diagnostic below will still work.
|
| 171 |
+
mod = importlib.import_module("transformers_modules.LocateAnything-3B.modeling_qwen2")
|
| 172 |
+
lines.append(f"remote_qwen2_module={getattr(mod, '__file__', None)}")
|
| 173 |
+
lines.append(f"remote_qwen2._MAGI_AVAILABLE={getattr(mod, '_MAGI_AVAILABLE', '<missing>')!r}")
|
| 174 |
+
lines.append(f"remote_qwen2.flex_flash_attn_func={getattr(mod, 'flex_flash_attn_func', '<missing>')}")
|
| 175 |
+
except Exception as e:
|
| 176 |
+
lines.append(f"remote_qwen2: diagnostic failed {type(e).__name__}: {e}")
|
| 177 |
+
return "\n".join(lines)
|
| 178 |
+
|
| 179 |
+
def _attn_class_diag(model):
|
| 180 |
+
try:
|
| 181 |
+
llm = model.language_model.model
|
| 182 |
+
classes = [type(layer.self_attn).__name__ for layer in llm.layers[:4]]
|
| 183 |
+
return (
|
| 184 |
+
f"llm._attn_implementation={getattr(llm, '_attn_implementation', None)!r}\n"
|
| 185 |
+
f"config._attn_implementation={getattr(llm.config, '_attn_implementation', None)!r}\n"
|
| 186 |
+
f"first_attn_classes={classes}"
|
| 187 |
+
)
|
| 188 |
+
except Exception as e:
|
| 189 |
+
return f"attention class diagnostic failed {type(e).__name__}: {e}"
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _set_vision_attention_mode(model):
|
| 193 |
+
"""Match HF's MoonViT policy: prefer flash_attention_2, then sdpa, then eager."""
|
| 194 |
+
vm = getattr(model, "vision_model", None)
|
| 195 |
+
if vm is None:
|
| 196 |
+
return None
|
| 197 |
+
mod = importlib.import_module(type(vm).__module__)
|
| 198 |
+
funcs = getattr(mod, "VL_VISION_ATTENTION_FUNCTIONS", {})
|
| 199 |
+
has_flash = getattr(mod, "flash_attn_varlen_func", None) is not None
|
| 200 |
+
requested = VISION_ATTN_MODE
|
| 201 |
+
|
| 202 |
+
if requested == "auto":
|
| 203 |
+
candidates = ("flash_attention_2", "sdpa", "eager")
|
| 204 |
+
else:
|
| 205 |
+
candidates = (requested, "flash_attention_2", "sdpa", "eager")
|
| 206 |
+
|
| 207 |
+
chosen = None
|
| 208 |
+
for candidate in candidates:
|
| 209 |
+
if candidate == "flash_attention_2" and not has_flash:
|
| 210 |
+
continue
|
| 211 |
+
if candidate in funcs:
|
| 212 |
+
chosen = candidate
|
| 213 |
+
break
|
| 214 |
+
if chosen is None:
|
| 215 |
+
raise RuntimeError("MoonViT has no supported attention implementation.")
|
| 216 |
+
|
| 217 |
+
if requested == "flash_attention_2" and chosen != "flash_attention_2":
|
| 218 |
+
warnings.warn("LA_FLASH_VISION_ATTN=flash_attention_2 requested but flash-attn is unavailable; "
|
| 219 |
+
f"using {chosen}.")
|
| 220 |
+
elif requested not in {"auto", chosen}:
|
| 221 |
+
warnings.warn(f"LA_FLASH_VISION_ATTN={requested} is unavailable; using {chosen}.")
|
| 222 |
+
|
| 223 |
+
if hasattr(model.config, "vision_config"):
|
| 224 |
+
model.config.vision_config._attn_implementation = chosen
|
| 225 |
+
try:
|
| 226 |
+
vm.config._attn_implementation = chosen
|
| 227 |
+
except Exception:
|
| 228 |
+
pass
|
| 229 |
+
try:
|
| 230 |
+
for block in vm.encoder.blocks:
|
| 231 |
+
block.attn_implementation = chosen
|
| 232 |
+
except Exception as exc:
|
| 233 |
+
raise RuntimeError("Failed to configure MoonViT attention implementation.") from exc
|
| 234 |
+
model._la_flash_vision_attn = chosen
|
| 235 |
+
return chosen
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def load():
|
| 239 |
+
"""Lazy model load with HF remote-code semantics plus release backends.
|
| 240 |
+
|
| 241 |
+
The text decoder is pinned to one of sdpa/eager/magi/la_flash. MoonViT is
|
| 242 |
+
configured independently and follows the HF policy: flash_attention_2 when
|
| 243 |
+
flash-attn is importable, otherwise sdpa, otherwise eager.
|
| 244 |
+
"""
|
| 245 |
+
global _tok, _proc, _model
|
| 246 |
+
if _model is None:
|
| 247 |
+
_tok = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
|
| 248 |
+
_proc = AutoProcessor.from_pretrained(MODEL, trust_remote_code=True)
|
| 249 |
+
attn_impl = REMOTE_ATTN_MODE
|
| 250 |
+
if ATTN_MODE == "magi" and os.environ.get("LA_FLASH_DEBUG", "0") != "0":
|
| 251 |
+
print("LA Flash magi pre-load diagnostic:", flush=True)
|
| 252 |
+
print(_magi_diag(), flush=True)
|
| 253 |
+
_model = AutoModel.from_pretrained(MODEL, torch_dtype=DT, trust_remote_code=True,
|
| 254 |
+
attn_implementation=attn_impl).to(DEV).eval()
|
| 255 |
+
_set_vision_attention_mode(_model)
|
| 256 |
+
actual_attn = getattr(_model.language_model.model, "_attn_implementation", None)
|
| 257 |
+
if ATTN_MODE == "magi" and os.environ.get("LA_FLASH_DEBUG", "0") != "0":
|
| 258 |
+
print("LA Flash magi post-load diagnostic:", flush=True)
|
| 259 |
+
print(_remote_magi_diag(_model), flush=True)
|
| 260 |
+
print(_attn_class_diag(_model), flush=True)
|
| 261 |
+
if ATTN_MODE == "magi":
|
| 262 |
+
try:
|
| 263 |
+
qwen2_mod = importlib.import_module(type(_model.language_model.model).__module__)
|
| 264 |
+
if not getattr(qwen2_mod, "_MAGI_AVAILABLE", False):
|
| 265 |
+
raise RuntimeError(
|
| 266 |
+
"remote module reports _MAGI_AVAILABLE=False.\n"
|
| 267 |
+
f"{_remote_magi_diag(_model)}\n{_magi_diag()}"
|
| 268 |
+
)
|
| 269 |
+
first_attn = type(_model.language_model.model.layers[0].self_attn).__name__
|
| 270 |
+
if actual_attn != "sdpa" or first_attn != "_BatchedMagiAttention":
|
| 271 |
+
_set_llm_mode(_model, "magi")
|
| 272 |
+
actual_attn = getattr(_model.language_model.model, "_attn_implementation", None)
|
| 273 |
+
first_attn = type(_model.language_model.model.layers[0].self_attn).__name__
|
| 274 |
+
if os.environ.get("LA_FLASH_DEBUG", "0") != "0":
|
| 275 |
+
print("LA Flash magi post-swap diagnostic:", flush=True)
|
| 276 |
+
print(_attn_class_diag(_model), flush=True)
|
| 277 |
+
if actual_attn != "sdpa" or first_attn != "_BatchedMagiAttention":
|
| 278 |
+
raise RuntimeError(
|
| 279 |
+
"batched magi attention did not activate. "
|
| 280 |
+
f"actual_attn={actual_attn!r}; first_attn={first_attn!r}; "
|
| 281 |
+
f"{_remote_magi_diag(_model)}; {_attn_class_diag(_model)}"
|
| 282 |
+
)
|
| 283 |
+
_model._la_flash_requested_attn = "magi"
|
| 284 |
+
except Exception as exc:
|
| 285 |
+
_fallback_to_sdpa(_model, "magi", exc)
|
| 286 |
+
else:
|
| 287 |
+
try:
|
| 288 |
+
_set_llm_mode(_model, ATTN_MODE) # decode-safe mask plumbing for sdpa/eager/la_flash
|
| 289 |
+
except Exception as exc:
|
| 290 |
+
_fallback_to_sdpa(_model, ATTN_MODE, exc)
|
| 291 |
+
if MTP_COMPILE:
|
| 292 |
+
_maybe_compile(_model)
|
| 293 |
+
return _tok, _proc, _model
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def _maybe_compile(model):
|
| 297 |
+
"""Compile the shared Qwen2Model core (base.forward). It backs BOTH prefill (called directly)
|
| 298 |
+
and decode (language_model.forward -> self.model). lm_head + MoonViT left eager. dynamic=True
|
| 299 |
+
so the varying decode S/kvlen don't trigger a recompile storm. No-op + warning if triton is
|
| 300 |
+
missing (inductor needs it on GPU). First call pays the compile cost (~42s warm / ~187s cold)."""
|
| 301 |
+
try:
|
| 302 |
+
import triton # noqa: F401
|
| 303 |
+
except Exception:
|
| 304 |
+
warnings.warn("MTP_COMPILE set but triton is unavailable; running without torch.compile.")
|
| 305 |
+
return
|
| 306 |
+
import torch._dynamo as _dyn
|
| 307 |
+
_dyn.config.cache_size_limit = max(_dyn.config.cache_size_limit, 64)
|
| 308 |
+
base = model.language_model.model
|
| 309 |
+
if not getattr(base, "_mtp_compiled", False):
|
| 310 |
+
base.forward = torch.compile(base.forward, dynamic=True)
|
| 311 |
+
base._mtp_compiled = True
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def build_batched_magi_attention_class(mod):
|
| 315 |
+
"""Build a Qwen2 attention subclass backed by Magi's flex_flash_attn.
|
| 316 |
+
|
| 317 |
+
The official LocateAnything ``Qwen2MagiAttention`` asserts ``bsz == 1`` and
|
| 318 |
+
relies on ``Qwen2Model._attn_implementation == "magi"`` to build a single
|
| 319 |
+
sample range plan. For release batch inference the hybrid scheduler passes
|
| 320 |
+
a batched Magi range plan directly to this layer; a 4D-mask conversion path
|
| 321 |
+
remains as a compatibility fallback.
|
| 322 |
+
"""
|
| 323 |
+
flex_flash_attn_func = getattr(mod, "flex_flash_attn_func", None)
|
| 324 |
+
if flex_flash_attn_func is None:
|
| 325 |
+
try:
|
| 326 |
+
from magi_attention.functional.flex_flash_attn import flex_flash_attn_func
|
| 327 |
+
except Exception as exc:
|
| 328 |
+
raise RuntimeError(
|
| 329 |
+
"LA_FLASH_ATTN=magi requires "
|
| 330 |
+
"magi_attention.functional.flex_flash_attn.flex_flash_attn_func."
|
| 331 |
+
) from exc
|
| 332 |
+
|
| 333 |
+
FULL, CAUSAL = 0, 1
|
| 334 |
+
causal_plan_cache = {}
|
| 335 |
+
try:
|
| 336 |
+
magi_params = set(inspect.signature(flex_flash_attn_func).parameters)
|
| 337 |
+
except (TypeError, ValueError):
|
| 338 |
+
magi_params = set()
|
| 339 |
+
supports_disable_fwd_atomic = "disable_fwd_atomic_reduction" in magi_params
|
| 340 |
+
|
| 341 |
+
def _disjoint_q_ranges(q_ranges):
|
| 342 |
+
seen = set()
|
| 343 |
+
for start, end in q_ranges:
|
| 344 |
+
key = (int(start), int(end))
|
| 345 |
+
if key in seen:
|
| 346 |
+
return False
|
| 347 |
+
seen.add(key)
|
| 348 |
+
return True
|
| 349 |
+
|
| 350 |
+
def _plan_disjoint_q_ranges(plan):
|
| 351 |
+
cached = plan.get("_la_flash_disjoint_q_ranges")
|
| 352 |
+
if cached is not None:
|
| 353 |
+
return bool(cached)
|
| 354 |
+
q_ranges = plan["q_ranges"].detach().to(device="cpu", dtype=torch.int32).tolist()
|
| 355 |
+
disjoint = _disjoint_q_ranges(q_ranges)
|
| 356 |
+
try:
|
| 357 |
+
plan["_la_flash_disjoint_q_ranges"] = disjoint
|
| 358 |
+
except Exception:
|
| 359 |
+
pass
|
| 360 |
+
return disjoint
|
| 361 |
+
|
| 362 |
+
def _tensor_plan(q_ranges, k_ranges, types, device):
|
| 363 |
+
return {
|
| 364 |
+
"q_ranges": torch.tensor(q_ranges, dtype=torch.int32, device=device).contiguous(),
|
| 365 |
+
"k_ranges": torch.tensor(k_ranges, dtype=torch.int32, device=device).contiguous(),
|
| 366 |
+
"attn_type_map": torch.tensor(types, dtype=torch.int32, device=device).contiguous(),
|
| 367 |
+
"_la_flash_disjoint_q_ranges": _disjoint_q_ranges(q_ranges),
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
def _offset_plan(plan, q_offset, k_offset):
|
| 371 |
+
return (
|
| 372 |
+
(plan["q_ranges"] + int(q_offset)).tolist(),
|
| 373 |
+
(plan["k_ranges"] + int(k_offset)).tolist(),
|
| 374 |
+
plan["attn_type_map"].tolist(),
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
def _causal_plan(bsz, q_len, kv_seq_len, device):
|
| 378 |
+
key = (int(bsz), int(q_len), int(kv_seq_len), device.type, device.index)
|
| 379 |
+
cached = causal_plan_cache.get(key)
|
| 380 |
+
if cached is not None:
|
| 381 |
+
return cached
|
| 382 |
+
q_ranges, k_ranges, types = [], [], []
|
| 383 |
+
for b in range(int(bsz)):
|
| 384 |
+
q_base = b * int(q_len)
|
| 385 |
+
k_base = b * int(kv_seq_len)
|
| 386 |
+
q_ranges.append([q_base, q_base + int(q_len)])
|
| 387 |
+
k_ranges.append([k_base, k_base + int(kv_seq_len)])
|
| 388 |
+
types.append(CAUSAL)
|
| 389 |
+
plan = _tensor_plan(q_ranges, k_ranges, types, device)
|
| 390 |
+
plan.update(
|
| 391 |
+
{
|
| 392 |
+
"flash_cu_seqlens_q": torch.arange(
|
| 393 |
+
0,
|
| 394 |
+
(int(bsz) + 1) * int(q_len),
|
| 395 |
+
int(q_len),
|
| 396 |
+
dtype=torch.int32,
|
| 397 |
+
device=device,
|
| 398 |
+
),
|
| 399 |
+
"flash_cu_seqlens_k": torch.arange(
|
| 400 |
+
0,
|
| 401 |
+
(int(bsz) + 1) * int(kv_seq_len),
|
| 402 |
+
int(kv_seq_len),
|
| 403 |
+
dtype=torch.int32,
|
| 404 |
+
device=device,
|
| 405 |
+
),
|
| 406 |
+
"flash_causal": True,
|
| 407 |
+
}
|
| 408 |
+
)
|
| 409 |
+
causal_plan_cache[key] = plan
|
| 410 |
+
return plan
|
| 411 |
+
|
| 412 |
+
def _row_segments(row):
|
| 413 |
+
idx = np.flatnonzero(row)
|
| 414 |
+
if idx.size == 0:
|
| 415 |
+
return ((0, 1),)
|
| 416 |
+
split = np.flatnonzero(np.diff(idx) > 1) + 1
|
| 417 |
+
starts = np.concatenate((idx[:1], idx[split]))
|
| 418 |
+
ends = np.concatenate((idx[split - 1], idx[-1:])) + 1
|
| 419 |
+
return tuple((int(s), int(e)) for s, e in zip(starts, ends))
|
| 420 |
+
|
| 421 |
+
def _visible_from_4d_mask(attention_mask, kv_seq_len):
|
| 422 |
+
mask = attention_mask[:, :, :, :kv_seq_len]
|
| 423 |
+
if mask.dtype == torch.bool:
|
| 424 |
+
return mask[:, 0].detach().to(device="cpu", dtype=torch.bool).contiguous()
|
| 425 |
+
mask_cpu = mask[:, 0].detach().to(device="cpu").contiguous()
|
| 426 |
+
if getattr(attention_mask, "_la_flash_visible_mask", False):
|
| 427 |
+
return (mask_cpu > 0).to(dtype=torch.bool)
|
| 428 |
+
|
| 429 |
+
max_value = float(mask_cpu.max().item()) if mask_cpu.numel() else 0.0
|
| 430 |
+
min_value = float(mask_cpu.min().item()) if mask_cpu.numel() else 0.0
|
| 431 |
+
if max_value > 0.0 and min_value >= 0.0:
|
| 432 |
+
return (mask_cpu > 0).to(dtype=torch.bool)
|
| 433 |
+
return (mask_cpu >= 0).to(dtype=torch.bool)
|
| 434 |
+
|
| 435 |
+
def _plan_from_visible_mask(attention_mask, bsz, q_len, kv_seq_len, device):
|
| 436 |
+
cache_key = (int(bsz), int(q_len), int(kv_seq_len), device.type, device.index)
|
| 437 |
+
cached = getattr(attention_mask, "_la_flash_magi_plan", None)
|
| 438 |
+
if cached is not None and cached[0] == cache_key:
|
| 439 |
+
return cached[1]
|
| 440 |
+
|
| 441 |
+
visible = _visible_from_4d_mask(attention_mask, int(kv_seq_len)).numpy()
|
| 442 |
+
q_ranges, k_ranges, types = [], [], []
|
| 443 |
+
for b in range(int(bsz)):
|
| 444 |
+
q_base = b * int(q_len)
|
| 445 |
+
k_base = b * int(kv_seq_len)
|
| 446 |
+
run_start = 0
|
| 447 |
+
run_segments = _row_segments(visible[b, 0])
|
| 448 |
+
for q in range(1, int(q_len)):
|
| 449 |
+
segments = _row_segments(visible[b, q])
|
| 450 |
+
if segments == run_segments:
|
| 451 |
+
continue
|
| 452 |
+
for start, end in run_segments:
|
| 453 |
+
q_ranges.append([q_base + run_start, q_base + q])
|
| 454 |
+
k_ranges.append([k_base + start, k_base + end])
|
| 455 |
+
types.append(FULL)
|
| 456 |
+
run_start = q
|
| 457 |
+
run_segments = segments
|
| 458 |
+
for start, end in run_segments:
|
| 459 |
+
q_ranges.append([q_base + run_start, q_base + int(q_len)])
|
| 460 |
+
k_ranges.append([k_base + start, k_base + end])
|
| 461 |
+
types.append(FULL)
|
| 462 |
+
|
| 463 |
+
plan = _tensor_plan(q_ranges, k_ranges, types, device)
|
| 464 |
+
try:
|
| 465 |
+
attention_mask._la_flash_magi_plan = (cache_key, plan)
|
| 466 |
+
except Exception:
|
| 467 |
+
pass
|
| 468 |
+
return plan
|
| 469 |
+
|
| 470 |
+
def _plan_from_magi_dict(attention_mask, bsz, q_len, kv_seq_len, device):
|
| 471 |
+
if int(bsz) == 1:
|
| 472 |
+
return attention_mask
|
| 473 |
+
q_ranges, k_ranges, types = [], [], []
|
| 474 |
+
for b in range(int(bsz)):
|
| 475 |
+
qs, ks, ts = _offset_plan(
|
| 476 |
+
attention_mask,
|
| 477 |
+
q_offset=b * int(q_len),
|
| 478 |
+
k_offset=b * int(kv_seq_len),
|
| 479 |
+
)
|
| 480 |
+
q_ranges.extend(qs)
|
| 481 |
+
k_ranges.extend(ks)
|
| 482 |
+
types.extend(ts)
|
| 483 |
+
return _tensor_plan(q_ranges, k_ranges, types, device)
|
| 484 |
+
|
| 485 |
+
def _magi_plan(attention_mask, bsz, q_len, kv_seq_len, device):
|
| 486 |
+
if isinstance(attention_mask, dict):
|
| 487 |
+
if attention_mask.get("_la_flash_batched", False):
|
| 488 |
+
return attention_mask
|
| 489 |
+
return _plan_from_magi_dict(attention_mask, bsz, q_len, kv_seq_len, device)
|
| 490 |
+
if attention_mask is None:
|
| 491 |
+
return _causal_plan(bsz, q_len, kv_seq_len, device)
|
| 492 |
+
if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):
|
| 493 |
+
raise ValueError(
|
| 494 |
+
f"Attention mask should be of size {(bsz, 1, q_len, kv_seq_len)}, "
|
| 495 |
+
f"but is {attention_mask.size()}"
|
| 496 |
+
)
|
| 497 |
+
return _plan_from_visible_mask(attention_mask, bsz, q_len, kv_seq_len, device)
|
| 498 |
+
|
| 499 |
+
class _BatchedMagiAttention(mod.Qwen2Attention):
|
| 500 |
+
"""MagiAttention path with true batch inference via packed token ranges."""
|
| 501 |
+
|
| 502 |
+
def forward(
|
| 503 |
+
self,
|
| 504 |
+
hidden_states: torch.Tensor,
|
| 505 |
+
attention_mask=None,
|
| 506 |
+
position_ids=None,
|
| 507 |
+
past_key_value=None,
|
| 508 |
+
output_attentions=False,
|
| 509 |
+
use_cache=False,
|
| 510 |
+
**kwargs,
|
| 511 |
+
):
|
| 512 |
+
if output_attentions:
|
| 513 |
+
raise NotImplementedError("MagiAttention does not support output_attentions=True")
|
| 514 |
+
|
| 515 |
+
bsz, q_len, _ = hidden_states.size()
|
| 516 |
+
query_states = self.q_proj(hidden_states)
|
| 517 |
+
key_states = self.k_proj(hidden_states)
|
| 518 |
+
value_states = self.v_proj(hidden_states)
|
| 519 |
+
|
| 520 |
+
query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 521 |
+
key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
|
| 522 |
+
value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
|
| 523 |
+
|
| 524 |
+
kv_seq_len = key_states.shape[-2]
|
| 525 |
+
if past_key_value is not None:
|
| 526 |
+
if self.layer_idx is None:
|
| 527 |
+
raise ValueError(
|
| 528 |
+
f"The cache structure has changed since version v4.36. If you are using "
|
| 529 |
+
f"{self.__class__.__name__} for auto-regressive decoding with k/v caching, "
|
| 530 |
+
"please initialize the attention class with a layer index."
|
| 531 |
+
)
|
| 532 |
+
kv_seq_len += past_key_value.get_seq_length(self.layer_idx)
|
| 533 |
+
|
| 534 |
+
cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)
|
| 535 |
+
query_states, key_states = mod.apply_rotary_pos_emb(
|
| 536 |
+
query_states, key_states, cos, sin, position_ids)
|
| 537 |
+
|
| 538 |
+
if past_key_value is not None:
|
| 539 |
+
cache_kwargs = {"sin": sin, "cos": cos}
|
| 540 |
+
key_states, value_states = past_key_value.update(
|
| 541 |
+
key_states, value_states, self.layer_idx, cache_kwargs)
|
| 542 |
+
|
| 543 |
+
kv_seq_len = key_states.shape[-2]
|
| 544 |
+
plan = _magi_plan(attention_mask, bsz, q_len, kv_seq_len, query_states.device)
|
| 545 |
+
magi_extra_kwargs = {}
|
| 546 |
+
if supports_disable_fwd_atomic:
|
| 547 |
+
magi_extra_kwargs["disable_fwd_atomic_reduction"] = (
|
| 548 |
+
(not self.training) and _plan_disjoint_q_ranges(plan)
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
query_states = query_states.transpose(1, 2).reshape(
|
| 552 |
+
bsz * q_len, self.num_heads, self.head_dim).contiguous()
|
| 553 |
+
key_states = key_states.transpose(1, 2).reshape(
|
| 554 |
+
bsz * kv_seq_len, self.num_key_value_heads, self.head_dim).contiguous()
|
| 555 |
+
value_states = value_states.transpose(1, 2).reshape(
|
| 556 |
+
bsz * kv_seq_len, self.num_key_value_heads, self.head_dim).contiguous()
|
| 557 |
+
|
| 558 |
+
attn_output, _ = flex_flash_attn_func(
|
| 559 |
+
query_states,
|
| 560 |
+
key_states,
|
| 561 |
+
value_states,
|
| 562 |
+
q_ranges=plan["q_ranges"],
|
| 563 |
+
k_ranges=plan["k_ranges"],
|
| 564 |
+
attn_type_map=plan["attn_type_map"],
|
| 565 |
+
softmax_scale=getattr(self, "softmax_scale", self.head_dim ** -0.5),
|
| 566 |
+
softcap=0.0,
|
| 567 |
+
deterministic=False,
|
| 568 |
+
**magi_extra_kwargs,
|
| 569 |
+
)
|
| 570 |
+
attn_output = attn_output.view(bsz, q_len, self.hidden_size)
|
| 571 |
+
attn_output = self.o_proj(attn_output)
|
| 572 |
+
return attn_output, None, past_key_value
|
| 573 |
+
|
| 574 |
+
return _BatchedMagiAttention
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
def build_la_flash_attention_class(mod):
|
| 578 |
+
"""Build a Qwen2 attention subclass backed by LA Flash sparse ranges."""
|
| 579 |
+
try:
|
| 580 |
+
from kernel_utils import is_available, range_attention
|
| 581 |
+
except Exception as exc:
|
| 582 |
+
raise RuntimeError(
|
| 583 |
+
"LA_FLASH_ATTN=la_flash requires kernel_utils and FlashAttention."
|
| 584 |
+
) from exc
|
| 585 |
+
if not is_available():
|
| 586 |
+
raise RuntimeError(
|
| 587 |
+
"LA_FLASH_ATTN=la_flash requires flash_attn.flash_attn_varlen_func."
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
FULL, CAUSAL = 0, 1
|
| 591 |
+
causal_plan_cache = {}
|
| 592 |
+
|
| 593 |
+
def _tensor_plan(q_ranges, k_ranges, types, device):
|
| 594 |
+
max_q_len = max((int(end) - int(start) for start, end in q_ranges), default=0)
|
| 595 |
+
max_k_len = max((int(end) - int(start) for start, end in k_ranges), default=0)
|
| 596 |
+
plan = {
|
| 597 |
+
"q_ranges": torch.tensor(q_ranges, dtype=torch.int32, device=device).contiguous(),
|
| 598 |
+
"k_ranges": torch.tensor(k_ranges, dtype=torch.int32, device=device).contiguous(),
|
| 599 |
+
"attn_type_map": torch.tensor(types, dtype=torch.int32, device=device).contiguous(),
|
| 600 |
+
"max_q_len": max_q_len,
|
| 601 |
+
"max_k_len": max_k_len,
|
| 602 |
+
}
|
| 603 |
+
plan.update(_la_flash_group_plan_tensors(q_ranges, types, device))
|
| 604 |
+
return plan
|
| 605 |
+
|
| 606 |
+
def _offset_plan(plan, q_offset, k_offset):
|
| 607 |
+
return (
|
| 608 |
+
(plan["q_ranges"] + int(q_offset)).tolist(),
|
| 609 |
+
(plan["k_ranges"] + int(k_offset)).tolist(),
|
| 610 |
+
plan["attn_type_map"].tolist(),
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
def _causal_plan(bsz, q_len, kv_seq_len, device):
|
| 614 |
+
key = (int(bsz), int(q_len), int(kv_seq_len), device.type, device.index)
|
| 615 |
+
cached = causal_plan_cache.get(key)
|
| 616 |
+
if cached is not None:
|
| 617 |
+
return cached
|
| 618 |
+
q_ranges, k_ranges, types = [], [], []
|
| 619 |
+
for b in range(int(bsz)):
|
| 620 |
+
q_base = b * int(q_len)
|
| 621 |
+
k_base = b * int(kv_seq_len)
|
| 622 |
+
q_ranges.append([q_base, q_base + int(q_len)])
|
| 623 |
+
k_ranges.append([k_base, k_base + int(kv_seq_len)])
|
| 624 |
+
types.append(CAUSAL)
|
| 625 |
+
plan = _tensor_plan(q_ranges, k_ranges, types, device)
|
| 626 |
+
plan.update(
|
| 627 |
+
{
|
| 628 |
+
"flash_cu_seqlens_q": torch.arange(
|
| 629 |
+
0,
|
| 630 |
+
(int(bsz) + 1) * int(q_len),
|
| 631 |
+
int(q_len),
|
| 632 |
+
dtype=torch.int32,
|
| 633 |
+
device=device,
|
| 634 |
+
),
|
| 635 |
+
"flash_cu_seqlens_k": torch.arange(
|
| 636 |
+
0,
|
| 637 |
+
(int(bsz) + 1) * int(kv_seq_len),
|
| 638 |
+
int(kv_seq_len),
|
| 639 |
+
dtype=torch.int32,
|
| 640 |
+
device=device,
|
| 641 |
+
),
|
| 642 |
+
"flash_causal": True,
|
| 643 |
+
}
|
| 644 |
+
)
|
| 645 |
+
causal_plan_cache[key] = plan
|
| 646 |
+
return plan
|
| 647 |
+
|
| 648 |
+
def _row_segments(row):
|
| 649 |
+
idx = np.flatnonzero(row)
|
| 650 |
+
if idx.size == 0:
|
| 651 |
+
return ((0, 1),)
|
| 652 |
+
split = np.flatnonzero(np.diff(idx) > 1) + 1
|
| 653 |
+
starts = np.concatenate((idx[:1], idx[split]))
|
| 654 |
+
ends = np.concatenate((idx[split - 1], idx[-1:])) + 1
|
| 655 |
+
return tuple((int(s), int(e)) for s, e in zip(starts, ends))
|
| 656 |
+
|
| 657 |
+
def _visible_from_4d_mask(attention_mask, kv_seq_len):
|
| 658 |
+
mask = attention_mask[:, :, :, :kv_seq_len]
|
| 659 |
+
if mask.dtype == torch.bool:
|
| 660 |
+
return mask[:, 0].detach().to(device="cpu", dtype=torch.bool).contiguous()
|
| 661 |
+
mask_cpu = mask[:, 0].detach().to(device="cpu").contiguous()
|
| 662 |
+
if getattr(attention_mask, "_la_flash_visible_mask", False):
|
| 663 |
+
return (mask_cpu > 0).to(dtype=torch.bool)
|
| 664 |
+
|
| 665 |
+
max_value = float(mask_cpu.max().item()) if mask_cpu.numel() else 0.0
|
| 666 |
+
min_value = float(mask_cpu.min().item()) if mask_cpu.numel() else 0.0
|
| 667 |
+
if max_value > 0.0 and min_value >= 0.0:
|
| 668 |
+
return (mask_cpu > 0).to(dtype=torch.bool)
|
| 669 |
+
return (mask_cpu >= 0).to(dtype=torch.bool)
|
| 670 |
+
|
| 671 |
+
def _prefix_len(row):
|
| 672 |
+
idx = np.flatnonzero(row)
|
| 673 |
+
if idx.size == 0:
|
| 674 |
+
return None
|
| 675 |
+
end = int(idx[-1]) + 1
|
| 676 |
+
if not bool(row[:end].all()) or bool(row[end:].any()):
|
| 677 |
+
return None
|
| 678 |
+
return end
|
| 679 |
+
|
| 680 |
+
def _causal_plan_from_visible(visible, bsz, q_len, kv_seq_len, device):
|
| 681 |
+
q_ranges, k_ranges, types = [], [], []
|
| 682 |
+
packed_flash = True
|
| 683 |
+
for b in range(int(bsz)):
|
| 684 |
+
first_len = _prefix_len(visible[b, 0])
|
| 685 |
+
if first_len is None:
|
| 686 |
+
return None
|
| 687 |
+
valid_len = int(first_len) + int(q_len) - 1
|
| 688 |
+
if valid_len < int(q_len) or valid_len > int(kv_seq_len):
|
| 689 |
+
return None
|
| 690 |
+
for q in range(int(q_len)):
|
| 691 |
+
row_len = _prefix_len(visible[b, q])
|
| 692 |
+
expected = valid_len - int(q_len) + q + 1
|
| 693 |
+
if row_len != expected:
|
| 694 |
+
return None
|
| 695 |
+
q_base = b * int(q_len)
|
| 696 |
+
k_base = b * int(kv_seq_len)
|
| 697 |
+
q_ranges.append([q_base, q_base + int(q_len)])
|
| 698 |
+
k_ranges.append([k_base, k_base + valid_len])
|
| 699 |
+
types.append(CAUSAL)
|
| 700 |
+
packed_flash = packed_flash and valid_len == int(kv_seq_len)
|
| 701 |
+
|
| 702 |
+
plan = _tensor_plan(q_ranges, k_ranges, types, device)
|
| 703 |
+
plan["_la_flash_disjoint_q_ranges"] = True
|
| 704 |
+
if packed_flash:
|
| 705 |
+
plan.update(
|
| 706 |
+
{
|
| 707 |
+
"flash_cu_seqlens_q": torch.arange(
|
| 708 |
+
0,
|
| 709 |
+
(int(bsz) + 1) * int(q_len),
|
| 710 |
+
int(q_len),
|
| 711 |
+
dtype=torch.int32,
|
| 712 |
+
device=device,
|
| 713 |
+
),
|
| 714 |
+
"flash_cu_seqlens_k": torch.arange(
|
| 715 |
+
0,
|
| 716 |
+
(int(bsz) + 1) * int(kv_seq_len),
|
| 717 |
+
int(kv_seq_len),
|
| 718 |
+
dtype=torch.int32,
|
| 719 |
+
device=device,
|
| 720 |
+
),
|
| 721 |
+
"flash_causal": True,
|
| 722 |
+
}
|
| 723 |
+
)
|
| 724 |
+
return plan
|
| 725 |
+
|
| 726 |
+
def _plan_from_visible_mask(attention_mask, bsz, q_len, kv_seq_len, device):
|
| 727 |
+
cache_key = (int(bsz), int(q_len), int(kv_seq_len), device.type, device.index, "la_flash")
|
| 728 |
+
cached = getattr(attention_mask, "_la_flash_range_plan", None)
|
| 729 |
+
if cached is not None and cached[0] == cache_key:
|
| 730 |
+
return cached[1]
|
| 731 |
+
|
| 732 |
+
visible = _visible_from_4d_mask(attention_mask, int(kv_seq_len)).numpy()
|
| 733 |
+
plan = _causal_plan_from_visible(visible, bsz, q_len, kv_seq_len, device)
|
| 734 |
+
if plan is not None:
|
| 735 |
+
try:
|
| 736 |
+
attention_mask._la_flash_range_plan = (cache_key, plan)
|
| 737 |
+
except Exception:
|
| 738 |
+
pass
|
| 739 |
+
return plan
|
| 740 |
+
|
| 741 |
+
q_ranges, k_ranges, types = [], [], []
|
| 742 |
+
for b in range(int(bsz)):
|
| 743 |
+
q_base = b * int(q_len)
|
| 744 |
+
k_base = b * int(kv_seq_len)
|
| 745 |
+
run_start = 0
|
| 746 |
+
run_segments = _row_segments(visible[b, 0])
|
| 747 |
+
for q in range(1, int(q_len)):
|
| 748 |
+
segments = _row_segments(visible[b, q])
|
| 749 |
+
if segments == run_segments:
|
| 750 |
+
continue
|
| 751 |
+
for start, end in run_segments:
|
| 752 |
+
q_ranges.append([q_base + run_start, q_base + q])
|
| 753 |
+
k_ranges.append([k_base + start, k_base + end])
|
| 754 |
+
types.append(FULL)
|
| 755 |
+
run_start = q
|
| 756 |
+
run_segments = segments
|
| 757 |
+
for start, end in run_segments:
|
| 758 |
+
q_ranges.append([q_base + run_start, q_base + int(q_len)])
|
| 759 |
+
k_ranges.append([k_base + start, k_base + end])
|
| 760 |
+
types.append(FULL)
|
| 761 |
+
|
| 762 |
+
plan = _tensor_plan(q_ranges, k_ranges, types, device)
|
| 763 |
+
try:
|
| 764 |
+
attention_mask._la_flash_range_plan = (cache_key, plan)
|
| 765 |
+
except Exception:
|
| 766 |
+
pass
|
| 767 |
+
return plan
|
| 768 |
+
|
| 769 |
+
def _plan_from_magi_dict(attention_mask, bsz, q_len, kv_seq_len, device):
|
| 770 |
+
if int(bsz) == 1:
|
| 771 |
+
return attention_mask
|
| 772 |
+
q_ranges, k_ranges, types = [], [], []
|
| 773 |
+
for b in range(int(bsz)):
|
| 774 |
+
qs, ks, ts = _offset_plan(
|
| 775 |
+
attention_mask,
|
| 776 |
+
q_offset=b * int(q_len),
|
| 777 |
+
k_offset=b * int(kv_seq_len),
|
| 778 |
+
)
|
| 779 |
+
q_ranges.extend(qs)
|
| 780 |
+
k_ranges.extend(ks)
|
| 781 |
+
types.extend(ts)
|
| 782 |
+
return _tensor_plan(q_ranges, k_ranges, types, device)
|
| 783 |
+
|
| 784 |
+
def _range_plan(attention_mask, bsz, q_len, kv_seq_len, device):
|
| 785 |
+
if isinstance(attention_mask, dict):
|
| 786 |
+
if attention_mask.get("_la_flash_batched", False):
|
| 787 |
+
return attention_mask
|
| 788 |
+
return _plan_from_magi_dict(attention_mask, bsz, q_len, kv_seq_len, device)
|
| 789 |
+
if attention_mask is None:
|
| 790 |
+
return _causal_plan(bsz, q_len, kv_seq_len, device)
|
| 791 |
+
if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):
|
| 792 |
+
raise ValueError(
|
| 793 |
+
f"Attention mask should be of size {(bsz, 1, q_len, kv_seq_len)}, "
|
| 794 |
+
f"but is {attention_mask.size()}"
|
| 795 |
+
)
|
| 796 |
+
return _plan_from_visible_mask(attention_mask, bsz, q_len, kv_seq_len, device)
|
| 797 |
+
|
| 798 |
+
class _LaFlashAttention(mod.Qwen2Attention):
|
| 799 |
+
"""Range-plan attention path backed by FlashAttention sparse ranges."""
|
| 800 |
+
|
| 801 |
+
def forward(
|
| 802 |
+
self,
|
| 803 |
+
hidden_states: torch.Tensor,
|
| 804 |
+
attention_mask=None,
|
| 805 |
+
position_ids=None,
|
| 806 |
+
past_key_value=None,
|
| 807 |
+
output_attentions=False,
|
| 808 |
+
use_cache=False,
|
| 809 |
+
**kwargs,
|
| 810 |
+
):
|
| 811 |
+
if output_attentions:
|
| 812 |
+
raise NotImplementedError("LA Flash attention does not support output_attentions=True")
|
| 813 |
+
|
| 814 |
+
bsz, q_len, _ = hidden_states.size()
|
| 815 |
+
query_states = self.q_proj(hidden_states)
|
| 816 |
+
key_states = self.k_proj(hidden_states)
|
| 817 |
+
value_states = self.v_proj(hidden_states)
|
| 818 |
+
|
| 819 |
+
query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 820 |
+
key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
|
| 821 |
+
value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
|
| 822 |
+
|
| 823 |
+
kv_seq_len = key_states.shape[-2]
|
| 824 |
+
if past_key_value is not None:
|
| 825 |
+
if self.layer_idx is None:
|
| 826 |
+
raise ValueError(
|
| 827 |
+
f"The cache structure has changed since version v4.36. If you are using "
|
| 828 |
+
f"{self.__class__.__name__} for auto-regressive decoding with k/v caching, "
|
| 829 |
+
"please initialize the attention class with a layer index."
|
| 830 |
+
)
|
| 831 |
+
kv_seq_len += past_key_value.get_seq_length(self.layer_idx)
|
| 832 |
+
|
| 833 |
+
cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)
|
| 834 |
+
query_states, key_states = mod.apply_rotary_pos_emb(
|
| 835 |
+
query_states, key_states, cos, sin, position_ids)
|
| 836 |
+
|
| 837 |
+
if past_key_value is not None:
|
| 838 |
+
cache_kwargs = {"sin": sin, "cos": cos}
|
| 839 |
+
key_states, value_states = past_key_value.update(
|
| 840 |
+
key_states, value_states, self.layer_idx, cache_kwargs)
|
| 841 |
+
|
| 842 |
+
kv_seq_len = key_states.shape[-2]
|
| 843 |
+
dense_backend = os.environ.get("LA_FLASH_DENSE_BACKEND", "sdpa").strip().lower()
|
| 844 |
+
if dense_backend == "sdpa" and not isinstance(attention_mask, dict):
|
| 845 |
+
dense_key_states = mod.repeat_kv(key_states, self.num_key_value_groups)
|
| 846 |
+
dense_value_states = mod.repeat_kv(value_states, self.num_key_value_groups)
|
| 847 |
+
if attention_mask is not None:
|
| 848 |
+
if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):
|
| 849 |
+
raise ValueError(
|
| 850 |
+
f"Attention mask should be of size {(bsz, 1, q_len, kv_seq_len)}, "
|
| 851 |
+
f"but is {attention_mask.size()}"
|
| 852 |
+
)
|
| 853 |
+
query_for_sdpa = query_states.contiguous()
|
| 854 |
+
key_for_sdpa = dense_key_states.contiguous()
|
| 855 |
+
value_for_sdpa = dense_value_states.contiguous()
|
| 856 |
+
is_causal = False
|
| 857 |
+
elif past_key_value is None:
|
| 858 |
+
query_for_sdpa = query_states
|
| 859 |
+
key_for_sdpa = dense_key_states
|
| 860 |
+
value_for_sdpa = dense_value_states
|
| 861 |
+
is_causal = bool(self.is_causal and q_len > 1)
|
| 862 |
+
else:
|
| 863 |
+
query_for_sdpa = key_for_sdpa = value_for_sdpa = None
|
| 864 |
+
is_causal = False
|
| 865 |
+
if query_for_sdpa is not None:
|
| 866 |
+
attn_output = torch.nn.functional.scaled_dot_product_attention(
|
| 867 |
+
query_for_sdpa,
|
| 868 |
+
key_for_sdpa,
|
| 869 |
+
value_for_sdpa,
|
| 870 |
+
attn_mask=attention_mask,
|
| 871 |
+
dropout_p=self.attention_dropout if self.training else 0.0,
|
| 872 |
+
is_causal=is_causal,
|
| 873 |
+
)
|
| 874 |
+
attn_output = attn_output.transpose(1, 2).contiguous()
|
| 875 |
+
attn_output = attn_output.reshape(bsz, q_len, self.hidden_size)
|
| 876 |
+
attn_output = self.o_proj(attn_output)
|
| 877 |
+
return attn_output, None, past_key_value
|
| 878 |
+
|
| 879 |
+
plan = _range_plan(attention_mask, bsz, q_len, kv_seq_len, query_states.device)
|
| 880 |
+
|
| 881 |
+
query_states = query_states.transpose(1, 2).reshape(
|
| 882 |
+
bsz * q_len, self.num_heads, self.head_dim).contiguous()
|
| 883 |
+
key_states = key_states.transpose(1, 2).reshape(
|
| 884 |
+
bsz * kv_seq_len, self.num_key_value_heads, self.head_dim).contiguous()
|
| 885 |
+
value_states = value_states.transpose(1, 2).reshape(
|
| 886 |
+
bsz * kv_seq_len, self.num_key_value_heads, self.head_dim).contiguous()
|
| 887 |
+
|
| 888 |
+
attn_output = range_attention(
|
| 889 |
+
query_states,
|
| 890 |
+
key_states,
|
| 891 |
+
value_states,
|
| 892 |
+
plan["q_ranges"],
|
| 893 |
+
plan["k_ranges"],
|
| 894 |
+
plan["attn_type_map"],
|
| 895 |
+
getattr(self, "softmax_scale", self.head_dim ** -0.5),
|
| 896 |
+
segment_offsets=plan.get("segment_offsets"),
|
| 897 |
+
group_q_ranges=plan.get("group_q_ranges"),
|
| 898 |
+
group_attn_type_map=plan.get("group_attn_type_map"),
|
| 899 |
+
max_q_len=plan.get("max_q_len"),
|
| 900 |
+
max_k_len=plan.get("max_k_len"),
|
| 901 |
+
flash_cu_seqlens_q=plan.get("flash_cu_seqlens_q"),
|
| 902 |
+
flash_cu_seqlens_k=plan.get("flash_cu_seqlens_k"),
|
| 903 |
+
flash_causal=plan.get("flash_causal"),
|
| 904 |
+
disjoint_q_ranges=plan.get("_la_flash_disjoint_q_ranges"),
|
| 905 |
+
)
|
| 906 |
+
attn_output = attn_output.view(bsz, q_len, self.hidden_size)
|
| 907 |
+
attn_output = self.o_proj(attn_output)
|
| 908 |
+
return attn_output, None, past_key_value
|
| 909 |
+
|
| 910 |
+
return _LaFlashAttention
|
| 911 |
+
|
| 912 |
+
|
| 913 |
+
def _is_magi_plan(obj):
|
| 914 |
+
return isinstance(obj, dict) and {
|
| 915 |
+
"q_ranges",
|
| 916 |
+
"k_ranges",
|
| 917 |
+
"attn_type_map",
|
| 918 |
+
}.issubset(obj.keys())
|
| 919 |
+
|
| 920 |
+
|
| 921 |
+
def _la_flash_group_plan_tensors(q_ranges, types, device):
|
| 922 |
+
"""Group consecutive Magi range entries that share the same query span.
|
| 923 |
+
|
| 924 |
+
Magi-style plans may represent one query span with multiple disjoint key
|
| 925 |
+
spans. LA Flash consumes those as one FlashAttention-backed softmax group.
|
| 926 |
+
"""
|
| 927 |
+
if not q_ranges:
|
| 928 |
+
return {
|
| 929 |
+
"group_q_ranges": torch.empty((0, 2), dtype=torch.int32, device=device),
|
| 930 |
+
"segment_offsets": torch.zeros((1,), dtype=torch.int32, device=device),
|
| 931 |
+
"group_attn_type_map": torch.empty((0,), dtype=torch.int32, device=device),
|
| 932 |
+
}
|
| 933 |
+
|
| 934 |
+
grouped_q, grouped_types, offsets = [], [], [0]
|
| 935 |
+
last_q = None
|
| 936 |
+
last_type = None
|
| 937 |
+
for idx, (q_range, attn_type) in enumerate(zip(q_ranges, types)):
|
| 938 |
+
key = (int(q_range[0]), int(q_range[1]))
|
| 939 |
+
attn_type = int(attn_type)
|
| 940 |
+
if last_q is None:
|
| 941 |
+
grouped_q.append([key[0], key[1]])
|
| 942 |
+
grouped_types.append(attn_type)
|
| 943 |
+
last_q = key
|
| 944 |
+
last_type = attn_type
|
| 945 |
+
continue
|
| 946 |
+
if key == last_q and attn_type == last_type:
|
| 947 |
+
continue
|
| 948 |
+
offsets.append(idx)
|
| 949 |
+
grouped_q.append([key[0], key[1]])
|
| 950 |
+
grouped_types.append(attn_type)
|
| 951 |
+
last_q = key
|
| 952 |
+
last_type = attn_type
|
| 953 |
+
offsets.append(len(q_ranges))
|
| 954 |
+
|
| 955 |
+
return {
|
| 956 |
+
"group_q_ranges": torch.tensor(grouped_q, dtype=torch.int32, device=device).contiguous(),
|
| 957 |
+
"segment_offsets": torch.tensor(offsets, dtype=torch.int32, device=device).contiguous(),
|
| 958 |
+
"group_attn_type_map": torch.tensor(grouped_types, dtype=torch.int32, device=device).contiguous(),
|
| 959 |
+
"max_q_len": max((end - start for start, end in grouped_q), default=0),
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
|
| 963 |
+
def _record_sparse_plan_stats(model, q_ranges, k_ranges, types):
|
| 964 |
+
if os.environ.get("LA_FLASH_PLAN_STATS", "0") != "1":
|
| 965 |
+
return
|
| 966 |
+
stats = getattr(model, "_la_flash_sparse_plan_stats", None)
|
| 967 |
+
if stats is None:
|
| 968 |
+
stats = {
|
| 969 |
+
"calls": 0,
|
| 970 |
+
"ranges": 0,
|
| 971 |
+
"q_tokens": 0,
|
| 972 |
+
"k_tokens": 0,
|
| 973 |
+
"max_q_len": 0,
|
| 974 |
+
"max_k_len": 0,
|
| 975 |
+
"full_ranges": 0,
|
| 976 |
+
"causal_ranges": 0,
|
| 977 |
+
"other_ranges": 0,
|
| 978 |
+
}
|
| 979 |
+
model._la_flash_sparse_plan_stats = stats
|
| 980 |
+
stats["calls"] += 1
|
| 981 |
+
stats["ranges"] += len(q_ranges)
|
| 982 |
+
for (q_start, q_end), (k_start, k_end), attn_type in zip(q_ranges, k_ranges, types):
|
| 983 |
+
q_len = int(q_end) - int(q_start)
|
| 984 |
+
k_len = int(k_end) - int(k_start)
|
| 985 |
+
stats["q_tokens"] += q_len
|
| 986 |
+
stats["k_tokens"] += k_len
|
| 987 |
+
stats["max_q_len"] = max(stats["max_q_len"], q_len)
|
| 988 |
+
stats["max_k_len"] = max(stats["max_k_len"], k_len)
|
| 989 |
+
attn_type = int(attn_type)
|
| 990 |
+
if attn_type == 0:
|
| 991 |
+
stats["full_ranges"] += 1
|
| 992 |
+
elif attn_type == 1:
|
| 993 |
+
stats["causal_ranges"] += 1
|
| 994 |
+
else:
|
| 995 |
+
stats["other_ranges"] += 1
|
| 996 |
+
|
| 997 |
+
|
| 998 |
+
def build_magi_scheduler_ranges(model, attention_mask_2d, input_ids, past_len, mtp_window=False):
|
| 999 |
+
"""Build batched Magi ranges directly from the hybrid scheduler mask.
|
| 1000 |
+
|
| 1001 |
+
The official Qwen2 SDPA dispatcher may optimize an all-valid 2D mask to
|
| 1002 |
+
``None`` before decoder layers see it. That is correct for plain causal
|
| 1003 |
+
attention but loses LocateAnything's MTP generation-window rule. Building
|
| 1004 |
+
ranges here keeps Magi batch inference exact and avoids per-layer dense
|
| 1005 |
+
mask conversion.
|
| 1006 |
+
"""
|
| 1007 |
+
requested_attn = getattr(model, "_la_flash_requested_attn", ATTN_MODE)
|
| 1008 |
+
if requested_attn not in {"magi", "la_flash"}:
|
| 1009 |
+
return None
|
| 1010 |
+
if attention_mask_2d is None or not hasattr(attention_mask_2d, "dim") or attention_mask_2d.dim() != 2:
|
| 1011 |
+
return None
|
| 1012 |
+
|
| 1013 |
+
bsz, q_len = int(input_ids.shape[0]), int(input_ids.shape[1])
|
| 1014 |
+
key_len = int(attention_mask_2d.shape[1])
|
| 1015 |
+
dev = input_ids.device
|
| 1016 |
+
llm = model.language_model.model
|
| 1017 |
+
block = int(getattr(llm, "block_size", N_FUTURE))
|
| 1018 |
+
causal_attn = bool(getattr(llm, "causal_attn", False))
|
| 1019 |
+
use_mtp_window = bool(mtp_window and q_len >= block and key_len >= block)
|
| 1020 |
+
q0 = max(0, q_len - block)
|
| 1021 |
+
k0 = max(0, key_len - block)
|
| 1022 |
+
blocked_k = k0 - 1
|
| 1023 |
+
past_len = int(past_len)
|
| 1024 |
+
|
| 1025 |
+
key_valid = attention_mask_2d.detach().to(device="cpu", dtype=torch.bool).contiguous().numpy()
|
| 1026 |
+
key_idx = np.arange(key_len)
|
| 1027 |
+
q_ranges, k_ranges, types = [], [], []
|
| 1028 |
+
if not use_mtp_window:
|
| 1029 |
+
causal_q_ranges, causal_k_ranges, causal_types = [], [], []
|
| 1030 |
+
causal_fast_path = True
|
| 1031 |
+
packed_flash = True
|
| 1032 |
+
for b in range(bsz):
|
| 1033 |
+
valid = np.flatnonzero(key_valid[b])
|
| 1034 |
+
if valid.size == 0:
|
| 1035 |
+
causal_fast_path = False
|
| 1036 |
+
break
|
| 1037 |
+
valid_len = int(valid[-1]) + 1
|
| 1038 |
+
if valid_len < q_len or not bool(key_valid[b, :valid_len].all()) or bool(key_valid[b, valid_len:].any()):
|
| 1039 |
+
causal_fast_path = False
|
| 1040 |
+
break
|
| 1041 |
+
packed_flash = packed_flash and valid_len == key_len
|
| 1042 |
+
q_base = b * q_len
|
| 1043 |
+
k_base = b * key_len
|
| 1044 |
+
causal_q_ranges.append([q_base, q_base + q_len])
|
| 1045 |
+
causal_k_ranges.append([k_base, k_base + valid_len])
|
| 1046 |
+
causal_types.append(1)
|
| 1047 |
+
if causal_fast_path:
|
| 1048 |
+
plan = {
|
| 1049 |
+
"q_ranges": torch.tensor(causal_q_ranges, dtype=torch.int32, device=dev).contiguous(),
|
| 1050 |
+
"k_ranges": torch.tensor(causal_k_ranges, dtype=torch.int32, device=dev).contiguous(),
|
| 1051 |
+
"attn_type_map": torch.tensor(causal_types, dtype=torch.int32, device=dev).contiguous(),
|
| 1052 |
+
"max_q_len": q_len,
|
| 1053 |
+
"max_k_len": max((end - start for start, end in causal_k_ranges), default=0),
|
| 1054 |
+
"_la_flash_batched": True,
|
| 1055 |
+
"_la_flash_disjoint_q_ranges": True,
|
| 1056 |
+
}
|
| 1057 |
+
if packed_flash:
|
| 1058 |
+
plan.update(
|
| 1059 |
+
{
|
| 1060 |
+
"flash_cu_seqlens_q": torch.arange(
|
| 1061 |
+
0,
|
| 1062 |
+
(bsz + 1) * q_len,
|
| 1063 |
+
q_len,
|
| 1064 |
+
dtype=torch.int32,
|
| 1065 |
+
device=dev,
|
| 1066 |
+
),
|
| 1067 |
+
"flash_cu_seqlens_k": torch.arange(
|
| 1068 |
+
0,
|
| 1069 |
+
(bsz + 1) * key_len,
|
| 1070 |
+
key_len,
|
| 1071 |
+
dtype=torch.int32,
|
| 1072 |
+
device=dev,
|
| 1073 |
+
),
|
| 1074 |
+
"flash_causal": True,
|
| 1075 |
+
}
|
| 1076 |
+
)
|
| 1077 |
+
plan.update(_la_flash_group_plan_tensors(causal_q_ranges, causal_types, dev))
|
| 1078 |
+
_record_sparse_plan_stats(model, causal_q_ranges, causal_k_ranges, causal_types)
|
| 1079 |
+
return plan
|
| 1080 |
+
|
| 1081 |
+
def row_segments(row):
|
| 1082 |
+
idx = np.flatnonzero(row)
|
| 1083 |
+
if idx.size == 0:
|
| 1084 |
+
return ((0, 1),)
|
| 1085 |
+
split = np.flatnonzero(np.diff(idx) > 1) + 1
|
| 1086 |
+
starts = np.concatenate((idx[:1], idx[split]))
|
| 1087 |
+
ends = np.concatenate((idx[split - 1], idx[-1:])) + 1
|
| 1088 |
+
return tuple((int(s), int(e)) for s, e in zip(starts, ends))
|
| 1089 |
+
|
| 1090 |
+
for b in range(bsz):
|
| 1091 |
+
q_base = b * q_len
|
| 1092 |
+
k_base = b * key_len
|
| 1093 |
+
run_start = 0
|
| 1094 |
+
run_segments = None
|
| 1095 |
+
if use_mtp_window and not causal_attn:
|
| 1096 |
+
prefix_q_len = q0
|
| 1097 |
+
prefix_k_end = past_len + prefix_q_len
|
| 1098 |
+
prefix_ok = (
|
| 1099 |
+
prefix_q_len > 0
|
| 1100 |
+
and prefix_k_end <= key_len
|
| 1101 |
+
and bool(key_valid[b, :prefix_k_end].all())
|
| 1102 |
+
)
|
| 1103 |
+
window_prefix_ok = blocked_k <= 0 or bool(key_valid[b, :blocked_k].all())
|
| 1104 |
+
window_ok = bool(key_valid[b, k0:key_len].all())
|
| 1105 |
+
if prefix_ok:
|
| 1106 |
+
q_ranges.append([q_base, q_base + prefix_q_len])
|
| 1107 |
+
k_ranges.append([k_base, k_base + prefix_k_end])
|
| 1108 |
+
types.append(1)
|
| 1109 |
+
run_start = prefix_q_len
|
| 1110 |
+
if run_start == prefix_q_len and prefix_q_len < q_len and window_prefix_ok and window_ok:
|
| 1111 |
+
if blocked_k > 0:
|
| 1112 |
+
q_ranges.append([q_base + prefix_q_len, q_base + q_len])
|
| 1113 |
+
k_ranges.append([k_base, k_base + blocked_k])
|
| 1114 |
+
types.append(0)
|
| 1115 |
+
q_ranges.append([q_base + prefix_q_len, q_base + q_len])
|
| 1116 |
+
k_ranges.append([k_base + k0, k_base + key_len])
|
| 1117 |
+
types.append(0)
|
| 1118 |
+
continue
|
| 1119 |
+
|
| 1120 |
+
for q in range(run_start, q_len):
|
| 1121 |
+
visible = key_valid[b] & (key_idx <= q + past_len)
|
| 1122 |
+
if use_mtp_window and q >= q0:
|
| 1123 |
+
if not causal_attn:
|
| 1124 |
+
visible = visible.copy()
|
| 1125 |
+
visible[k0:key_len] = key_valid[b, k0:key_len]
|
| 1126 |
+
if blocked_k >= 0:
|
| 1127 |
+
if visible.base is None:
|
| 1128 |
+
visible[blocked_k] = False
|
| 1129 |
+
else:
|
| 1130 |
+
visible = visible.copy()
|
| 1131 |
+
visible[blocked_k] = False
|
| 1132 |
+
segments = row_segments(visible)
|
| 1133 |
+
if run_segments is None:
|
| 1134 |
+
run_segments = segments
|
| 1135 |
+
continue
|
| 1136 |
+
if segments == run_segments:
|
| 1137 |
+
continue
|
| 1138 |
+
for start, end in run_segments:
|
| 1139 |
+
q_ranges.append([q_base + run_start, q_base + q])
|
| 1140 |
+
k_ranges.append([k_base + start, k_base + end])
|
| 1141 |
+
types.append(0)
|
| 1142 |
+
run_start = q
|
| 1143 |
+
run_segments = segments
|
| 1144 |
+
for start, end in run_segments:
|
| 1145 |
+
q_ranges.append([q_base + run_start, q_base + q_len])
|
| 1146 |
+
k_ranges.append([k_base + start, k_base + end])
|
| 1147 |
+
types.append(0)
|
| 1148 |
+
|
| 1149 |
+
seen_q_ranges = set()
|
| 1150 |
+
disjoint_q_ranges = True
|
| 1151 |
+
for start, end in q_ranges:
|
| 1152 |
+
key = (int(start), int(end))
|
| 1153 |
+
if key in seen_q_ranges:
|
| 1154 |
+
disjoint_q_ranges = False
|
| 1155 |
+
break
|
| 1156 |
+
seen_q_ranges.add(key)
|
| 1157 |
+
|
| 1158 |
+
plan = {
|
| 1159 |
+
"q_ranges": torch.tensor(q_ranges, dtype=torch.int32, device=dev).contiguous(),
|
| 1160 |
+
"k_ranges": torch.tensor(k_ranges, dtype=torch.int32, device=dev).contiguous(),
|
| 1161 |
+
"attn_type_map": torch.tensor(types, dtype=torch.int32, device=dev).contiguous(),
|
| 1162 |
+
"max_q_len": max((end - start for start, end in q_ranges), default=0),
|
| 1163 |
+
"max_k_len": max((end - start for start, end in k_ranges), default=0),
|
| 1164 |
+
"_la_flash_batched": True,
|
| 1165 |
+
"_la_flash_disjoint_q_ranges": disjoint_q_ranges,
|
| 1166 |
+
}
|
| 1167 |
+
plan.update(_la_flash_group_plan_tensors(q_ranges, types, dev))
|
| 1168 |
+
_record_sparse_plan_stats(model, q_ranges, k_ranges, types)
|
| 1169 |
+
return plan
|
| 1170 |
+
|
| 1171 |
+
|
| 1172 |
+
def _direct_base_forward(
|
| 1173 |
+
base,
|
| 1174 |
+
input_ids=None,
|
| 1175 |
+
visual_features=None,
|
| 1176 |
+
image_token_index=None,
|
| 1177 |
+
attention_mask=None,
|
| 1178 |
+
position_ids=None,
|
| 1179 |
+
past_key_values=None,
|
| 1180 |
+
inputs_embeds=None,
|
| 1181 |
+
use_cache=None,
|
| 1182 |
+
output_attentions=None,
|
| 1183 |
+
output_hidden_states=None,
|
| 1184 |
+
return_dict=None,
|
| 1185 |
+
):
|
| 1186 |
+
mod = importlib.import_module(type(base).__module__)
|
| 1187 |
+
output_attentions = output_attentions if output_attentions is not None else base.config.output_attentions
|
| 1188 |
+
output_hidden_states = (
|
| 1189 |
+
output_hidden_states if output_hidden_states is not None else base.config.output_hidden_states
|
| 1190 |
+
)
|
| 1191 |
+
use_cache = use_cache if use_cache is not None else base.config.use_cache
|
| 1192 |
+
|
| 1193 |
+
if input_ids is not None and inputs_embeds is not None:
|
| 1194 |
+
raise ValueError("You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time")
|
| 1195 |
+
if input_ids is not None:
|
| 1196 |
+
batch_size, seq_length = input_ids.shape
|
| 1197 |
+
elif inputs_embeds is not None:
|
| 1198 |
+
batch_size, seq_length, _ = inputs_embeds.shape
|
| 1199 |
+
else:
|
| 1200 |
+
raise ValueError("You have to specify either decoder_input_ids or decoder_inputs_embeds")
|
| 1201 |
+
|
| 1202 |
+
past_key_values_length = 0
|
| 1203 |
+
use_legacy_cache = False
|
| 1204 |
+
if use_cache:
|
| 1205 |
+
Cache = getattr(mod, "Cache")
|
| 1206 |
+
DynamicCache = getattr(mod, "DynamicCache")
|
| 1207 |
+
use_legacy_cache = not isinstance(past_key_values, Cache)
|
| 1208 |
+
if use_legacy_cache:
|
| 1209 |
+
if past_key_values is None:
|
| 1210 |
+
past_key_values = DynamicCache()
|
| 1211 |
+
else:
|
| 1212 |
+
past_key_values = DynamicCache.from_legacy_cache(past_key_values)
|
| 1213 |
+
past_key_values_length = past_key_values.get_seq_length()
|
| 1214 |
+
|
| 1215 |
+
if position_ids is None:
|
| 1216 |
+
dev = input_ids.device if input_ids is not None else inputs_embeds.device
|
| 1217 |
+
position_ids = torch.arange(
|
| 1218 |
+
past_key_values_length,
|
| 1219 |
+
seq_length + past_key_values_length,
|
| 1220 |
+
dtype=torch.long,
|
| 1221 |
+
device=dev,
|
| 1222 |
+
).unsqueeze(0).view(-1, seq_length)
|
| 1223 |
+
else:
|
| 1224 |
+
position_ids = position_ids.view(-1, seq_length).long()
|
| 1225 |
+
|
| 1226 |
+
if inputs_embeds is None:
|
| 1227 |
+
inputs_embeds = base.image_processing(input_ids, visual_features, image_token_index)
|
| 1228 |
+
|
| 1229 |
+
hidden_states = inputs_embeds
|
| 1230 |
+
all_hidden_states = () if output_hidden_states else None
|
| 1231 |
+
all_self_attns = () if output_attentions else None
|
| 1232 |
+
next_decoder_cache = None
|
| 1233 |
+
|
| 1234 |
+
for decoder_layer in base.layers:
|
| 1235 |
+
if output_hidden_states:
|
| 1236 |
+
all_hidden_states += (hidden_states,)
|
| 1237 |
+
layer_outputs = decoder_layer(
|
| 1238 |
+
hidden_states,
|
| 1239 |
+
attention_mask=attention_mask,
|
| 1240 |
+
position_ids=position_ids,
|
| 1241 |
+
past_key_value=past_key_values,
|
| 1242 |
+
output_attentions=output_attentions,
|
| 1243 |
+
use_cache=use_cache,
|
| 1244 |
+
)
|
| 1245 |
+
hidden_states = layer_outputs[0]
|
| 1246 |
+
if use_cache:
|
| 1247 |
+
next_decoder_cache = layer_outputs[2 if output_attentions else 1]
|
| 1248 |
+
if output_attentions:
|
| 1249 |
+
all_self_attns += (layer_outputs[1],)
|
| 1250 |
+
|
| 1251 |
+
hidden_states = base.norm(hidden_states)
|
| 1252 |
+
if output_hidden_states:
|
| 1253 |
+
all_hidden_states += (hidden_states,)
|
| 1254 |
+
next_cache = None
|
| 1255 |
+
if use_cache:
|
| 1256 |
+
next_cache = next_decoder_cache.to_legacy_cache() if use_legacy_cache else next_decoder_cache
|
| 1257 |
+
return SimpleNamespace(
|
| 1258 |
+
last_hidden_state=hidden_states,
|
| 1259 |
+
past_key_values=next_cache,
|
| 1260 |
+
hidden_states=all_hidden_states,
|
| 1261 |
+
attentions=all_self_attns,
|
| 1262 |
+
)
|
| 1263 |
+
|
| 1264 |
+
|
| 1265 |
+
def language_model_forward(model, **kwargs):
|
| 1266 |
+
"""Forward through the text LM, bypassing official dense-mask prep for sparse plans."""
|
| 1267 |
+
lm = model.language_model
|
| 1268 |
+
return_logits = kwargs.pop("return_logits", True)
|
| 1269 |
+
logits_slice = kwargs.pop("logits_slice", None)
|
| 1270 |
+
attention_mask = kwargs.get("attention_mask")
|
| 1271 |
+
use_direct_sparse = (
|
| 1272 |
+
getattr(model, "_la_flash_requested_attn", ATTN_MODE) in {"magi", "la_flash"}
|
| 1273 |
+
and _is_magi_plan(attention_mask)
|
| 1274 |
+
)
|
| 1275 |
+
if not use_direct_sparse:
|
| 1276 |
+
return lm(**kwargs)
|
| 1277 |
+
|
| 1278 |
+
labels = kwargs.pop("labels", None)
|
| 1279 |
+
if labels is not None:
|
| 1280 |
+
raise NotImplementedError("labels are not supported in the direct sparse-plan decode forward")
|
| 1281 |
+
output_attentions = kwargs.get("output_attentions", None)
|
| 1282 |
+
output_hidden_states = kwargs.get("output_hidden_states", None)
|
| 1283 |
+
base_out = _direct_base_forward(lm.model, **kwargs)
|
| 1284 |
+
logits = None
|
| 1285 |
+
if return_logits:
|
| 1286 |
+
hidden_states = base_out.last_hidden_state
|
| 1287 |
+
if logits_slice is not None:
|
| 1288 |
+
hidden_states = hidden_states[:, logits_slice, :]
|
| 1289 |
+
logits = lm.lm_head(hidden_states).float()
|
| 1290 |
+
return SimpleNamespace(
|
| 1291 |
+
logits=logits,
|
| 1292 |
+
past_key_values=base_out.past_key_values,
|
| 1293 |
+
hidden_states=base_out.hidden_states if output_hidden_states else None,
|
| 1294 |
+
attentions=base_out.attentions if output_attentions else None,
|
| 1295 |
+
)
|
| 1296 |
+
|
| 1297 |
+
|
| 1298 |
+
_EagerCls = _SdpaCls = _LaFlashCls = _MagiCls = None
|
| 1299 |
+
def _attn_classes(mode=None):
|
| 1300 |
+
"""Attention classes from the dynamic Qwen2 remote module.
|
| 1301 |
+
|
| 1302 |
+
The official Qwen2Model mask dispatcher only implements ``sdpa`` and
|
| 1303 |
+
single-row ``magi``. Eager, LA Flash, and batched Magi inference
|
| 1304 |
+
therefore swap the layer class while keeping the model's mask dispatcher
|
| 1305 |
+
pinned to ``sdpa``.
|
| 1306 |
+
"""
|
| 1307 |
+
global _EagerCls, _SdpaCls, _LaFlashCls, _MagiCls
|
| 1308 |
+
mode = _normalize_attn_mode(mode) if mode is not None else None
|
| 1309 |
+
if _SdpaCls is None:
|
| 1310 |
+
mod = importlib.import_module(type(_model.language_model.model).__module__)
|
| 1311 |
+
_EagerCls = mod.Qwen2Attention
|
| 1312 |
+
_SdpaCls = mod.Qwen2SdpaAttention
|
| 1313 |
+
else:
|
| 1314 |
+
mod = importlib.import_module(type(_model.language_model.model).__module__)
|
| 1315 |
+
if (mode is None or mode == "la_flash") and _LaFlashCls is None:
|
| 1316 |
+
_LaFlashCls = build_la_flash_attention_class(mod)
|
| 1317 |
+
if (mode is None or mode == "magi") and _MagiCls is None:
|
| 1318 |
+
_MagiCls = build_batched_magi_attention_class(mod) if getattr(mod, "_MAGI_AVAILABLE", False) else None
|
| 1319 |
+
return _EagerCls, _SdpaCls, _LaFlashCls, _MagiCls
|
| 1320 |
+
|
| 1321 |
+
def _set_llm_mode(model, mode):
|
| 1322 |
+
"""Swap every Qwen2 decoder layer's attention class.
|
| 1323 |
+
|
| 1324 |
+
Release backends keep ``Qwen2Model._attn_implementation='sdpa'`` so the
|
| 1325 |
+
official Qwen2 mask dispatcher stays available for dense-mask modes. The
|
| 1326 |
+
local ``la_flash`` and batched ``magi`` wrappers can also consume scheduler-built
|
| 1327 |
+
sparse plans directly, avoiding repeated per-layer dense mask conversion.
|
| 1328 |
+
"""
|
| 1329 |
+
mode = _normalize_attn_mode(mode)
|
| 1330 |
+
eager, sdpa, la_flash, magi = _attn_classes(mode)
|
| 1331 |
+
impl = "sdpa"
|
| 1332 |
+
if mode == "sdpa":
|
| 1333 |
+
cls = sdpa
|
| 1334 |
+
elif mode == "eager":
|
| 1335 |
+
cls = eager
|
| 1336 |
+
elif mode == "la_flash":
|
| 1337 |
+
cls = la_flash
|
| 1338 |
+
elif mode == "magi":
|
| 1339 |
+
if magi is None:
|
| 1340 |
+
raise RuntimeError("MagiAttention is unavailable in the current Python environment.")
|
| 1341 |
+
cls = magi
|
| 1342 |
+
else:
|
| 1343 |
+
raise ValueError(f"unknown LLM attention mode: {mode}")
|
| 1344 |
+
llm = model.language_model.model
|
| 1345 |
+
for lyr in llm.layers:
|
| 1346 |
+
lyr.self_attn.__class__ = cls
|
| 1347 |
+
if mode == "magi":
|
| 1348 |
+
lyr.self_attn.softmax_scale = lyr.self_attn.head_dim ** -0.5
|
| 1349 |
+
llm._attn_implementation = impl
|
| 1350 |
+
llm.config._attn_implementation = llm._attn_implementation
|
| 1351 |
+
if hasattr(model.config, "text_config"):
|
| 1352 |
+
model.config.text_config._attn_implementation = llm._attn_implementation
|
| 1353 |
+
model.config._attn_implementation = llm._attn_implementation
|
| 1354 |
+
model._la_flash_requested_attn = mode
|
| 1355 |
+
|
| 1356 |
+
_st = _hp = None
|
| 1357 |
+
def _helpers():
|
| 1358 |
+
"""The model's own sample_tokens / handle_pattern (the exact box decoders)."""
|
| 1359 |
+
global _st, _hp
|
| 1360 |
+
if _st is None:
|
| 1361 |
+
m = importlib.import_module(type(load()[2]).__module__)
|
| 1362 |
+
_st, _hp = m.sample_tokens, m.handle_pattern
|
| 1363 |
+
return _st, _hp
|
| 1364 |
+
|
| 1365 |
+
|
| 1366 |
+
_gu = None
|
| 1367 |
+
def _gen_utils():
|
| 1368 |
+
"""The model's generate_utils module (apply_repetition_penalty / top_p_logits / top_k_logits /
|
| 1369 |
+
decode_bbox_avg / decode_ref / dists) -- the pieces sample_tokens_batched reuses verbatim."""
|
| 1370 |
+
global _gu
|
| 1371 |
+
if _gu is None:
|
| 1372 |
+
m = importlib.import_module(type(load()[2]).__module__)
|
| 1373 |
+
_gu = importlib.import_module(m.sample_tokens.__module__)
|
| 1374 |
+
return _gu
|
| 1375 |
+
|
| 1376 |
+
|
| 1377 |
+
def _env_float(name, default):
|
| 1378 |
+
val = os.environ.get(name)
|
| 1379 |
+
if val is None or val.strip() == "":
|
| 1380 |
+
return float(default)
|
| 1381 |
+
return float(val)
|
| 1382 |
+
|
| 1383 |
+
|
| 1384 |
+
def _coord_fallback_mode():
|
| 1385 |
+
mode = os.environ.get("LA_FLASH_COORD_FALLBACK_MODE", "legacy").strip().lower().replace("-", "_")
|
| 1386 |
+
aliases = {
|
| 1387 |
+
"": "legacy",
|
| 1388 |
+
"official": "legacy",
|
| 1389 |
+
"range": "legacy",
|
| 1390 |
+
"spread": "legacy",
|
| 1391 |
+
"none": "off",
|
| 1392 |
+
"disable": "off",
|
| 1393 |
+
"disabled": "off",
|
| 1394 |
+
"entropy_variance": "uncertainty",
|
| 1395 |
+
"entropy_var": "uncertainty",
|
| 1396 |
+
"ent_var": "uncertainty",
|
| 1397 |
+
"entropy_std": "uncertainty",
|
| 1398 |
+
}
|
| 1399 |
+
mode = aliases.get(mode, mode)
|
| 1400 |
+
if mode not in {"legacy", "uncertainty", "off"}:
|
| 1401 |
+
raise ValueError(
|
| 1402 |
+
"LA_FLASH_COORD_FALLBACK_MODE must be one of legacy, uncertainty, off"
|
| 1403 |
+
)
|
| 1404 |
+
return mode
|
| 1405 |
+
|
| 1406 |
+
|
| 1407 |
+
def _coord_uncertainty_threshold(coord_start_token_id, coord_end_token_id):
|
| 1408 |
+
"""Return the coord uncertainty threshold in raw coord-token units.
|
| 1409 |
+
|
| 1410 |
+
Backward-compatible behavior:
|
| 1411 |
+
- LA_FLASH_COORD_UNCERTAINTY_THRESH > 1 is treated as raw coord-token RMSE.
|
| 1412 |
+
- LA_FLASH_COORD_UNCERTAINTY_THRESH <= 1 is treated as normalized by coord span.
|
| 1413 |
+
- LA_FLASH_COORD_UNCERTAINTY_NORM_THRESH is an explicit normalized override.
|
| 1414 |
+
"""
|
| 1415 |
+
coord_span = max(float(coord_end_token_id - coord_start_token_id + 1), 1.0)
|
| 1416 |
+
norm_val = os.environ.get("LA_FLASH_COORD_UNCERTAINTY_NORM_THRESH")
|
| 1417 |
+
if norm_val is not None and norm_val.strip() != "":
|
| 1418 |
+
return float(norm_val) * coord_span
|
| 1419 |
+
|
| 1420 |
+
val = os.environ.get("LA_FLASH_COORD_UNCERTAINTY_THRESH")
|
| 1421 |
+
if val is None or val.strip() == "":
|
| 1422 |
+
return 20.0
|
| 1423 |
+
threshold = float(val)
|
| 1424 |
+
if 0.0 < threshold <= 1.0:
|
| 1425 |
+
return threshold * coord_span
|
| 1426 |
+
return threshold
|
| 1427 |
+
|
| 1428 |
+
|
| 1429 |
+
def _decode_bbox_with_uncertainty(logits, probs, token_ids, keep_k=4, generation_mode="hybrid"):
|
| 1430 |
+
"""Decode an MTP box with configurable coord uncertainty fallback.
|
| 1431 |
+
|
| 1432 |
+
The default mode is the official LocateAnything rule. ``uncertainty`` keeps
|
| 1433 |
+
the same frame checks and top-k coord selection, but uses one scalar
|
| 1434 |
+
criterion per coordinate: the posterior RMSE of committing to the current
|
| 1435 |
+
MAP coordinate among valid coord candidates. This is the Bayes risk under
|
| 1436 |
+
squared coordinate error, so probabilities and token distances are folded
|
| 1437 |
+
into one threshold in coordinate-token units.
|
| 1438 |
+
"""
|
| 1439 |
+
gu = _gen_utils()
|
| 1440 |
+
mode = _coord_fallback_mode()
|
| 1441 |
+
if mode == "legacy" or generation_mode != "hybrid":
|
| 1442 |
+
return gu.decode_bbox_avg(logits, probs, token_ids, keep_k=keep_k, generation_mode=generation_mode)
|
| 1443 |
+
|
| 1444 |
+
coord_start_token_id = token_ids["coord_start_token_id"]
|
| 1445 |
+
coord_end_token_id = token_ids["coord_end_token_id"]
|
| 1446 |
+
box_start_token_id = token_ids["box_start_token_id"]
|
| 1447 |
+
box_end_token_id = token_ids["box_end_token_id"]
|
| 1448 |
+
none_token_id = token_ids["none_token_id"]
|
| 1449 |
+
null_token_id = token_ids["null_token_id"]
|
| 1450 |
+
device = logits.device
|
| 1451 |
+
|
| 1452 |
+
box_type = gu.is_valid_box_frame(
|
| 1453 |
+
probs,
|
| 1454 |
+
token_ids,
|
| 1455 |
+
start_thresh=_env_float("LA_FLASH_COORD_BOX_START_THRESH", 0.7),
|
| 1456 |
+
end_thresh=_env_float("LA_FLASH_COORD_BOX_END_THRESH", 0.2),
|
| 1457 |
+
topk=keep_k,
|
| 1458 |
+
)
|
| 1459 |
+
if box_type == "empty_box":
|
| 1460 |
+
return torch.tensor([
|
| 1461 |
+
box_start_token_id,
|
| 1462 |
+
none_token_id,
|
| 1463 |
+
box_end_token_id,
|
| 1464 |
+
null_token_id,
|
| 1465 |
+
null_token_id,
|
| 1466 |
+
null_token_id,
|
| 1467 |
+
], dtype=torch.long, device=device)
|
| 1468 |
+
if box_type == "illegal_box":
|
| 1469 |
+
return None
|
| 1470 |
+
|
| 1471 |
+
pos_probs, pos_ids = torch.topk(probs[1:5], k=keep_k, dim=-1)
|
| 1472 |
+
valid = (pos_ids >= coord_start_token_id) & (pos_ids <= coord_end_token_id)
|
| 1473 |
+
has_valid = valid.any(dim=-1)
|
| 1474 |
+
if not has_valid.all():
|
| 1475 |
+
return None
|
| 1476 |
+
|
| 1477 |
+
first_valid_idx = valid.long().argmax(dim=-1, keepdim=True)
|
| 1478 |
+
first_valid_ids = pos_ids.gather(-1, first_valid_idx).squeeze(-1)
|
| 1479 |
+
if mode == "off":
|
| 1480 |
+
final_coords = first_valid_ids
|
| 1481 |
+
else:
|
| 1482 |
+
valid_counts = valid.sum(dim=-1)
|
| 1483 |
+
valid_probs = torch.where(valid, pos_probs, torch.zeros_like(pos_probs))
|
| 1484 |
+
valid_mass = valid_probs.sum(dim=-1).clamp_min(1e-12)
|
| 1485 |
+
weights = valid_probs / valid_mass.unsqueeze(-1)
|
| 1486 |
+
coord_values = (pos_ids - coord_start_token_id).to(dtype=torch.float32)
|
| 1487 |
+
map_coord = (first_valid_ids - coord_start_token_id).to(dtype=torch.float32)
|
| 1488 |
+
uncertainty = (weights * (coord_values - map_coord.unsqueeze(-1)).pow(2)).sum(dim=-1).sqrt()
|
| 1489 |
+
is_abnormal = (
|
| 1490 |
+
(valid_counts > 1)
|
| 1491 |
+
& (uncertainty > _coord_uncertainty_threshold(coord_start_token_id, coord_end_token_id))
|
| 1492 |
+
)
|
| 1493 |
+
final_coords = torch.where(is_abnormal, torch.tensor(0, device=device), first_valid_ids)
|
| 1494 |
+
|
| 1495 |
+
start_t = torch.tensor([box_start_token_id], dtype=final_coords.dtype, device=device)
|
| 1496 |
+
end_t = torch.tensor([box_end_token_id], dtype=final_coords.dtype, device=device)
|
| 1497 |
+
return torch.cat([start_t, final_coords, end_t])
|
| 1498 |
+
|
| 1499 |
+
|
| 1500 |
+
def _apply_repetition_penalty_lowmem(logits, generated, repetition_penalty):
|
| 1501 |
+
"""Apply the stock repetition penalty without allocating a [B, S, V] mask."""
|
| 1502 |
+
if repetition_penalty == 1.0:
|
| 1503 |
+
return logits
|
| 1504 |
+
_, _, vocab_size = logits.shape
|
| 1505 |
+
for row in range(logits.shape[0]):
|
| 1506 |
+
valid_tokens = generated[row].unique()
|
| 1507 |
+
valid_tokens = valid_tokens[(valid_tokens >= 0) & (valid_tokens < vocab_size)]
|
| 1508 |
+
if valid_tokens.numel() == 0:
|
| 1509 |
+
continue
|
| 1510 |
+
row_logits = logits[row, :, valid_tokens]
|
| 1511 |
+
logits[row, :, valid_tokens] = torch.where(
|
| 1512 |
+
row_logits > 0,
|
| 1513 |
+
row_logits / repetition_penalty,
|
| 1514 |
+
row_logits * repetition_penalty,
|
| 1515 |
+
)
|
| 1516 |
+
return logits
|
| 1517 |
+
|
| 1518 |
+
|
| 1519 |
+
def _finite_logit_bounds(dtype):
|
| 1520 |
+
finfo = torch.finfo(dtype)
|
| 1521 |
+
return finfo.min, finfo.max
|
| 1522 |
+
|
| 1523 |
+
|
| 1524 |
+
def _finite_logits(logits):
|
| 1525 |
+
if not logits.dtype.is_floating_point:
|
| 1526 |
+
logits = logits.float()
|
| 1527 |
+
min_val, max_val = _finite_logit_bounds(logits.dtype)
|
| 1528 |
+
return torch.nan_to_num(logits, nan=min_val, posinf=max_val, neginf=min_val)
|
| 1529 |
+
|
| 1530 |
+
|
| 1531 |
+
def _finite_logits_(logits):
|
| 1532 |
+
if not logits.dtype.is_floating_point:
|
| 1533 |
+
return logits.float()
|
| 1534 |
+
min_val, max_val = _finite_logit_bounds(logits.dtype)
|
| 1535 |
+
return logits.nan_to_num_(nan=min_val, posinf=max_val, neginf=min_val)
|
| 1536 |
+
|
| 1537 |
+
|
| 1538 |
+
def _top_p_logits_slice_(logits, top_p):
|
| 1539 |
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
| 1540 |
+
cumulative_probs = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1)
|
| 1541 |
+
sorted_indices_to_remove = cumulative_probs > top_p
|
| 1542 |
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
| 1543 |
+
sorted_indices_to_remove[..., 0] = False
|
| 1544 |
+
|
| 1545 |
+
remove = torch.zeros_like(logits, dtype=torch.bool, device=logits.device)
|
| 1546 |
+
remove.scatter_(-1, sorted_indices, sorted_indices_to_remove)
|
| 1547 |
+
logits.masked_fill_(remove, torch.finfo(logits.dtype).min)
|
| 1548 |
+
return logits
|
| 1549 |
+
|
| 1550 |
+
|
| 1551 |
+
def _top_p_logits_(logits, top_p):
|
| 1552 |
+
"""In-place nucleus filtering with bounded sort workspace.
|
| 1553 |
+
|
| 1554 |
+
The MTP sampler uses logits shaped ``[B, 6, V]``. Top-p is independent for
|
| 1555 |
+
each row and each future position, so filtering one position at a time keeps
|
| 1556 |
+
the expensive sorted-index workspace at ``[B, V]`` instead of ``[B, 6, V]``.
|
| 1557 |
+
"""
|
| 1558 |
+
if logits.dim() == 3 and logits.shape[1] > 1:
|
| 1559 |
+
for pos in range(logits.shape[1]):
|
| 1560 |
+
_top_p_logits_slice_(logits[:, pos, :], top_p)
|
| 1561 |
+
return logits
|
| 1562 |
+
return _top_p_logits_slice_(logits, top_p)
|
| 1563 |
+
|
| 1564 |
+
|
| 1565 |
+
def _top_k_logits_(logits, top_k):
|
| 1566 |
+
"""In-place top-k filtering mirroring generate_utils.top_k_logits."""
|
| 1567 |
+
top_k = min(int(top_k), logits.size(-1))
|
| 1568 |
+
threshold = torch.topk(logits, top_k)[0][..., -1, None]
|
| 1569 |
+
logits.masked_fill_(logits < threshold, torch.finfo(logits.dtype).min)
|
| 1570 |
+
return logits
|
| 1571 |
+
|
| 1572 |
+
|
| 1573 |
+
def _safe_probs(filtered_logits):
|
| 1574 |
+
"""Softmax with CUDA-multinomial-safe cleanup and row-wise argmax fallback."""
|
| 1575 |
+
filtered_logits = _finite_logits(filtered_logits)
|
| 1576 |
+
probs = torch.softmax(filtered_logits, dim=-1, dtype=torch.float32)
|
| 1577 |
+
probs = torch.nan_to_num(probs, nan=0.0, posinf=0.0, neginf=0.0).clamp_min_(0.0)
|
| 1578 |
+
row_sum = probs.sum(dim=-1, keepdim=True)
|
| 1579 |
+
bad = (~torch.isfinite(row_sum)) | (row_sum <= 0)
|
| 1580 |
+
if bool(bad.any().item()):
|
| 1581 |
+
fallback = torch.zeros_like(probs)
|
| 1582 |
+
fallback.scatter_(-1, filtered_logits.argmax(dim=-1, keepdim=True), 1.0)
|
| 1583 |
+
probs = torch.where(bad, fallback, probs)
|
| 1584 |
+
row_sum = probs.sum(dim=-1, keepdim=True)
|
| 1585 |
+
return probs / row_sum.clamp_min(1.0e-20)
|
| 1586 |
+
|
| 1587 |
+
|
| 1588 |
+
def _sample_top_p_sorted_tokens(logits, top_p):
|
| 1589 |
+
"""Sample from top-p filtered logits without scattering back to vocab order."""
|
| 1590 |
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1)
|
| 1591 |
+
cumulative_probs = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1)
|
| 1592 |
+
remove = cumulative_probs > top_p
|
| 1593 |
+
remove[..., 1:] = remove[..., :-1].clone()
|
| 1594 |
+
remove[..., 0] = False
|
| 1595 |
+
sorted_logits.masked_fill_(remove, torch.finfo(sorted_logits.dtype).min)
|
| 1596 |
+
sorted_probs = _safe_probs(sorted_logits)
|
| 1597 |
+
sample_idx = sorted_probs.argmax(dim=-1)
|
| 1598 |
+
try:
|
| 1599 |
+
sample_idx = torch.distributions.Categorical(probs=sorted_probs).sample()
|
| 1600 |
+
except Exception:
|
| 1601 |
+
pass
|
| 1602 |
+
return sorted_indices.gather(-1, sample_idx.unsqueeze(-1)).squeeze(-1)
|
| 1603 |
+
|
| 1604 |
+
|
| 1605 |
+
@torch.no_grad()
|
| 1606 |
+
def sample_tokens_batched(logits, generated, token_ids, per_row_temp,
|
| 1607 |
+
repetition_penalty=1.0, top_p=None, top_k=None,
|
| 1608 |
+
keep_k_avg=4, generation_mode='fast'):
|
| 1609 |
+
"""Batched fork of generate_utils.sample_tokens for the MTP window [B,6,V]. The logits pipeline
|
| 1610 |
+
(rep-penalty / per-row temperature / top_p / top_k / softmax / sample) is ROW-INDEPENDENT, so run
|
| 1611 |
+
it ONCE over the whole batch instead of B times on [1,6,V] (the per-row san defeats batching by
|
| 1612 |
+
slicing wlogits[b:b+1]). Only the variable-length box ASSEMBLY (decode_bbox_avg -> ragged shapes,
|
| 1613 |
+
where sample_tokens' final torch.stack throws) stays per-row, returned as a LIST.
|
| 1614 |
+
|
| 1615 |
+
Equivalence to per-row san: every pipeline op reduces on dim=-1 only (never crosses the row dim),
|
| 1616 |
+
so row b's processed logits/probs are bit-identical to slicing first -> greedy (per_row_temp==0,
|
| 1617 |
+
argmax branch, no RNG) is BIT-EXACT. Under sampling, one batched Categorical changes the global
|
| 1618 |
+
RNG consumption order vs B per-row draws -> box-size jitter (blessed; greedy is the exact gate).
|
| 1619 |
+
apply_repetition_penalty already loops per-row internally, so passing the full [B,M] `generated`
|
| 1620 |
+
is row-correct. keep_k_avg/generation_mode mirror sample_tokens' decode_bbox_avg call EXACTLY
|
| 1621 |
+
(note: the per-row san passes keep_k=5 but decode_bbox_avg reads keep_k_avg, default 4 -- so 5 is
|
| 1622 |
+
a no-op there; we replicate keep_k_avg=4). Returns (x0[B,6], boxes: list of B 1-D LongTensors)."""
|
| 1623 |
+
gu = _gen_utils()
|
| 1624 |
+
B, S, V = logits.shape # S = N_FUTURE = 6
|
| 1625 |
+
if repetition_penalty != 1.0:
|
| 1626 |
+
logits = _apply_repetition_penalty_lowmem(logits, generated, repetition_penalty)
|
| 1627 |
+
t = per_row_temp.to(dtype=logits.dtype).view(B, 1, 1)
|
| 1628 |
+
sample_rows = per_row_temp > 0
|
| 1629 |
+
if bool(sample_rows.all().item()):
|
| 1630 |
+
logits.div_(t.clamp(min=1e-8))
|
| 1631 |
+
elif bool(sample_rows.any().item()):
|
| 1632 |
+
idx = sample_rows.nonzero(as_tuple=True)[0]
|
| 1633 |
+
logits[idx].div_(t[idx].clamp(min=1e-8))
|
| 1634 |
+
logits = _finite_logits_(logits)
|
| 1635 |
+
if top_p is not None and top_p < 1:
|
| 1636 |
+
logits = _top_p_logits_(logits, top_p)
|
| 1637 |
+
if top_k is not None and top_k > 0:
|
| 1638 |
+
logits = _top_k_logits_(logits, top_k)
|
| 1639 |
+
probs = _safe_probs(logits)
|
| 1640 |
+
x0 = probs.argmax(dim=-1) # [B,6]; greedy rows are final here
|
| 1641 |
+
samp = per_row_temp > 0
|
| 1642 |
+
if bool(samp.any()): # sampling rows: ONE batched Categorical draw
|
| 1643 |
+
idx = samp.nonzero(as_tuple=True)[0]
|
| 1644 |
+
try:
|
| 1645 |
+
x0[idx] = gu.dists.Categorical(probs=probs[idx]).sample()
|
| 1646 |
+
except Exception:
|
| 1647 |
+
pass # keep argmax (matches san's except: probs.max)
|
| 1648 |
+
boxes = []
|
| 1649 |
+
fallback = torch.zeros(1, dtype=x0.dtype, device=x0.device)
|
| 1650 |
+
for b in range(B): # variable-length box assembly (per-row, exact)
|
| 1651 |
+
db = _decode_bbox_with_uncertainty(
|
| 1652 |
+
logits[b], probs[b], token_ids,
|
| 1653 |
+
keep_k=keep_k_avg, generation_mode=generation_mode)
|
| 1654 |
+
if db is not None:
|
| 1655 |
+
boxes.append(db)
|
| 1656 |
+
else:
|
| 1657 |
+
ref = gu.decode_ref(logits[b], probs[b], token_ids)
|
| 1658 |
+
if ref is None:
|
| 1659 |
+
boxes.append(fallback)
|
| 1660 |
+
elif torch.is_tensor(ref):
|
| 1661 |
+
boxes.append(ref.to(dtype=x0.dtype, device=x0.device))
|
| 1662 |
+
else:
|
| 1663 |
+
boxes.append(torch.tensor(ref, dtype=x0.dtype, device=x0.device))
|
| 1664 |
+
return x0, boxes
|
| 1665 |
+
|
| 1666 |
+
|
| 1667 |
+
@torch.no_grad()
|
| 1668 |
+
def sample_next_tokens_batched(logits, generated, per_row_temp,
|
| 1669 |
+
repetition_penalty=1.0, top_p=None, top_k=None):
|
| 1670 |
+
"""Batched one-token sampler for AR repair rows.
|
| 1671 |
+
|
| 1672 |
+
This mirrors the row-independent part of ``sample_tokens`` for logits shaped
|
| 1673 |
+
``[B,1,V]``. It intentionally does not run bbox/ref assembly because AR mode
|
| 1674 |
+
only needs the next token before the state machine classifies it.
|
| 1675 |
+
"""
|
| 1676 |
+
gu = _gen_utils()
|
| 1677 |
+
if logits.dim() != 3 or logits.shape[1] != 1:
|
| 1678 |
+
raise ValueError(f"AR batched sampler expects logits [B,1,V], got {tuple(logits.shape)}")
|
| 1679 |
+
B = int(logits.shape[0])
|
| 1680 |
+
if repetition_penalty != 1.0:
|
| 1681 |
+
logits = _apply_repetition_penalty_lowmem(logits, generated, repetition_penalty)
|
| 1682 |
+
t = per_row_temp.to(dtype=logits.dtype).view(B, 1, 1)
|
| 1683 |
+
sample_rows = per_row_temp > 0
|
| 1684 |
+
if bool(sample_rows.all().item()):
|
| 1685 |
+
logits.div_(t.clamp(min=1e-8))
|
| 1686 |
+
elif bool(sample_rows.any().item()):
|
| 1687 |
+
idx = sample_rows.nonzero(as_tuple=True)[0]
|
| 1688 |
+
logits[idx].div_(t[idx].clamp(min=1e-8))
|
| 1689 |
+
logits = _finite_logits_(logits)
|
| 1690 |
+
sorted_top_p = os.environ.get("AR_SORTED_TOPP", "0") == "1"
|
| 1691 |
+
default_top_p = sorted_top_p and top_p is not None and top_p < 1 and (top_k is None or top_k <= 0)
|
| 1692 |
+
if default_top_p and bool(sample_rows.all().item()):
|
| 1693 |
+
return _sample_top_p_sorted_tokens(logits, top_p)
|
| 1694 |
+
if top_p is not None and top_p < 1:
|
| 1695 |
+
logits = _top_p_logits_(logits, top_p)
|
| 1696 |
+
if top_k is not None and top_k > 0:
|
| 1697 |
+
logits = _top_k_logits_(logits, top_k)
|
| 1698 |
+
probs = _safe_probs(logits)
|
| 1699 |
+
x0 = probs.argmax(dim=-1)
|
| 1700 |
+
if bool(sample_rows.any().item()):
|
| 1701 |
+
# Keep row-ordered sampling as the release default. A single batched
|
| 1702 |
+
# Categorical is faster, but it consumes RNG differently from stock AR
|
| 1703 |
+
# repair and can alter default-temperature termination behavior.
|
| 1704 |
+
for row in sample_rows.nonzero(as_tuple=True)[0].tolist():
|
| 1705 |
+
try:
|
| 1706 |
+
x0[row : row + 1] = gu.dists.Categorical(probs=probs[row : row + 1]).sample()
|
| 1707 |
+
except Exception:
|
| 1708 |
+
pass
|
| 1709 |
+
return x0
|
| 1710 |
+
|
| 1711 |
+
|
| 1712 |
+
def load_pil(p):
|
| 1713 |
+
from PIL import Image
|
| 1714 |
+
im = Image.open(p).convert("RGB"); w, h = im.size
|
| 1715 |
+
if max(w, h) > MAX_DIM:
|
| 1716 |
+
s = MAX_DIM / max(w, h); im = im.resize((max(1, round(w*s)), max(1, round(h*s))), Image.LANCZOS)
|
| 1717 |
+
return im
|
| 1718 |
+
|
| 1719 |
+
def _preproc_one(im):
|
| 1720 |
+
"""CPU-side processor for one image -> (pixel_values[bf16], grid[int32]). Split out of
|
| 1721 |
+
_encode_image so _encode_images can batch the GPU encode while preprocessing stays per-image."""
|
| 1722 |
+
tok, proc, model = load()
|
| 1723 |
+
msg = [{"role": "user", "content": [{"type": "image", "image": im}, {"type": "text", "text": "x"}]}]
|
| 1724 |
+
text = proc.py_apply_chat_template(msg, tokenize=False, add_generation_prompt=True)
|
| 1725 |
+
imgs, vids = proc.process_vision_info(msg)
|
| 1726 |
+
inp = proc(text=[text], images=imgs, videos=vids, return_tensors="pt").to(DEV)
|
| 1727 |
+
grid = inp.get("image_grid_hws")
|
| 1728 |
+
if isinstance(grid, np.ndarray): grid = torch.from_numpy(grid).to(DEV, dtype=torch.int32)
|
| 1729 |
+
return inp["pixel_values"].to(DT), grid
|
| 1730 |
+
|
| 1731 |
+
|
| 1732 |
+
def _vision_is_flash():
|
| 1733 |
+
"""True iff MoonViT will actually run flash_attn_varlen (so cross-image packing is
|
| 1734 |
+
block-diagonal = exact AND a win). If the vision blocks are on sdpa/eager, OR the flash
|
| 1735 |
+
wheel is absent (multihead_attention falls back to the dense-mask sdpa path), packing is
|
| 1736 |
+
O(S^2) N^2 -> caller must stay per-image."""
|
| 1737 |
+
vm = load()[2].vision_model
|
| 1738 |
+
mod = importlib.import_module(type(vm).__module__)
|
| 1739 |
+
if getattr(mod, "flash_attn_varlen_func", None) is None:
|
| 1740 |
+
return False
|
| 1741 |
+
try:
|
| 1742 |
+
return vm.encoder.blocks[0].attn_implementation == "flash_attention_2"
|
| 1743 |
+
except Exception:
|
| 1744 |
+
return False
|
| 1745 |
+
|
| 1746 |
+
|
| 1747 |
+
@torch.no_grad()
|
| 1748 |
+
def _encode_images(ims):
|
| 1749 |
+
"""N images -> list of [n_img_tokens, C] mlp1-projected visual_features, one per image
|
| 1750 |
+
(row-order). Drop-in for [_encode_image(im) for im in ims].
|
| 1751 |
+
|
| 1752 |
+
With flash present (_vision_is_flash) and N>1, packs images into
|
| 1753 |
+
extract_feature micro-batches: MoonViT's varlen cu_seqlens path is
|
| 1754 |
+
block-diagonal by image. Without flash, the dense SDPA fallback would scale
|
| 1755 |
+
with the packed total sequence length, so this function falls back to
|
| 1756 |
+
per-image encode. MTP_BATCH_VISION=0 also forces per-image encode."""
|
| 1757 |
+
tok, proc, model = load()
|
| 1758 |
+
pvs, grids = [], []
|
| 1759 |
+
for im in ims:
|
| 1760 |
+
pv, g = _preproc_one(im)
|
| 1761 |
+
pvs.append(pv); grids.append(g)
|
| 1762 |
+
if BATCH_VISION and len(ims) > 1 and _vision_is_flash():
|
| 1763 |
+
if VISION_ENCODE_BATCH_SIZE <= 0 or VISION_ENCODE_BATCH_SIZE >= len(ims):
|
| 1764 |
+
vit_list = model.extract_feature(torch.cat(pvs, dim=0), torch.cat(grids, dim=0))
|
| 1765 |
+
else:
|
| 1766 |
+
vit_list = []
|
| 1767 |
+
for start in range(0, len(ims), VISION_ENCODE_BATCH_SIZE):
|
| 1768 |
+
end = min(start + VISION_ENCODE_BATCH_SIZE, len(ims))
|
| 1769 |
+
vit_list.extend(
|
| 1770 |
+
model.extract_feature(
|
| 1771 |
+
torch.cat(pvs[start:end], dim=0),
|
| 1772 |
+
torch.cat(grids[start:end], dim=0),
|
| 1773 |
+
)
|
| 1774 |
+
)
|
| 1775 |
+
return [model.mlp1(v) for v in vit_list] # one [P_i, C] per image (patch_merger split)
|
| 1776 |
+
return [model.mlp1(torch.cat(model.extract_feature(pv, g), dim=0))
|
| 1777 |
+
for pv, g in zip(pvs, grids)] # per-image (flash absent / N==1 / forced off)
|
| 1778 |
+
|
| 1779 |
+
|
| 1780 |
+
@torch.no_grad()
|
| 1781 |
+
def _encode_image(im):
|
| 1782 |
+
"""Single-image convenience wrapper (single-image callers); = _encode_images([im])[0]
|
| 1783 |
+
(takes the per-image path inside _encode_images, so bit-identical to the original)."""
|
| 1784 |
+
return _encode_images([im])[0]
|
| 1785 |
+
|
| 1786 |
+
@torch.no_grad()
|
| 1787 |
+
def _tokenize(im, query):
|
| 1788 |
+
"""1-D prompt token ids for (image, query). Uses the model's own chat template."""
|
| 1789 |
+
tok, proc, model = load()
|
| 1790 |
+
msg = [{"role": "user", "content": [{"type": "image", "image": im},
|
| 1791 |
+
{"type": "text", "text": _PROMPT + query + "."}]}]
|
| 1792 |
+
text = proc.py_apply_chat_template(msg, tokenize=False, add_generation_prompt=True)
|
| 1793 |
+
imgs, vids = proc.process_vision_info(msg)
|
| 1794 |
+
return proc(text=[text], images=imgs, videos=vids, return_tensors="pt").to(DEV)["input_ids"][0]
|
| 1795 |
+
|
| 1796 |
+
|
| 1797 |
+
@torch.no_grad()
|
| 1798 |
+
def _tokenize_cached_image(query, image_token_count, im=None):
|
| 1799 |
+
"""Tokenize a prompt when the image token count is already known.
|
| 1800 |
+
|
| 1801 |
+
This keeps the processor's chat template, but directly expands ``<image-1>``
|
| 1802 |
+
from the cached visual feature length. It avoids re-running the CPU image
|
| 1803 |
+
processor for every category prompt that shares the same image.
|
| 1804 |
+
"""
|
| 1805 |
+
tok, proc, model = load()
|
| 1806 |
+
msg = [{"role": "user", "content": [{"type": "image", "image": im},
|
| 1807 |
+
{"type": "text", "text": _PROMPT + query + "."}]}]
|
| 1808 |
+
text = proc.py_apply_chat_template(msg, tokenize=False, add_generation_prompt=True)
|
| 1809 |
+
placeholder = f"<{getattr(proc, 'image_placeholder', 'image')}-1>"
|
| 1810 |
+
image_token = getattr(proc, "image_token", "<IMG_CONTEXT>")
|
| 1811 |
+
image_start = getattr(proc, "image_start_token", "<img>")
|
| 1812 |
+
image_end = getattr(proc, "image_end_token", "</img>")
|
| 1813 |
+
replacement = f"<image 1>{image_start}{image_token * int(image_token_count)}{image_end}"
|
| 1814 |
+
if placeholder not in text:
|
| 1815 |
+
raise ValueError(f"cached image placeholder {placeholder!r} was not found in chat template")
|
| 1816 |
+
text = text.replace(placeholder, replacement, 1)
|
| 1817 |
+
return tok([text], return_tensors="pt").to(DEV)["input_ids"][0]
|
| 1818 |
+
|
| 1819 |
+
|
| 1820 |
+
def _proc_full(im, query):
|
| 1821 |
+
"""Full processor dict (input_ids, attention_mask, pixel_values, image_grid_hws) —
|
| 1822 |
+
used by the bench to drive the STOCK generate for the equivalence check."""
|
| 1823 |
+
tok, proc, model = load()
|
| 1824 |
+
msg = [{"role": "user", "content": [{"type": "image", "image": im},
|
| 1825 |
+
{"type": "text", "text": _PROMPT + query + "."}]}]
|
| 1826 |
+
text = proc.py_apply_chat_template(msg, tokenize=False, add_generation_prompt=True)
|
| 1827 |
+
imgs, vids = proc.process_vision_info(msg)
|
| 1828 |
+
inp = proc(text=[text], images=imgs, videos=vids, return_tensors="pt").to(DEV)
|
| 1829 |
+
grid = inp.get("image_grid_hws")
|
| 1830 |
+
if isinstance(grid, np.ndarray): grid = torch.from_numpy(grid).to(DEV, dtype=torch.int32)
|
| 1831 |
+
inp["image_grid_hws"] = grid
|
| 1832 |
+
return inp
|
| 1833 |
+
|
| 1834 |
+
def _pad_generated(prompt_ids, gen_ids, img_tok, dev):
|
| 1835 |
+
"""Per-row [prompt + accepted] left-padded with the image token (already in every
|
| 1836 |
+
prompt -> .unique() unchanged -> repetition penalty identical to single-run)."""
|
| 1837 |
+
rows = [list(prompt_ids[b].tolist()) + gen_ids[b] for b in range(len(prompt_ids))]
|
| 1838 |
+
M = max(len(r) for r in rows)
|
| 1839 |
+
out = torch.full((len(rows), M), img_tok, dtype=torch.long, device=dev)
|
| 1840 |
+
for b, r in enumerate(rows):
|
| 1841 |
+
out[b, M - len(r):] = torch.tensor(r, dtype=torch.long, device=dev)
|
| 1842 |
+
return out
|
kernel_utils/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LA Flash Utils
|
| 2 |
+
|
| 3 |
+
This folder contains the sparse attention utilities used by
|
| 4 |
+
`LA_FLASH_ATTN=la_flash`. The release path is implemented with
|
| 5 |
+
FlashAttention varlen over LocateAnything range plans. It does not include or
|
| 6 |
+
build a local C++/CUDA extension.
|
| 7 |
+
|
| 8 |
+
## Features
|
| 9 |
+
|
| 10 |
+
- Supports batched LocateAnything hybrid MTP inference on A100, RTX 4090, and H100.
|
| 11 |
+
- Consumes Magi-style `q_ranges`, `k_ranges`, `segment_offsets`, and
|
| 12 |
+
`attn_type_map` plans generated by `batch_utils.hybrid_runtime`.
|
| 13 |
+
- Uses FlashAttention varlen for packed causal/full plans.
|
| 14 |
+
- Packs LocateAnything MTP full-window key segments before calling
|
| 15 |
+
FlashAttention, avoiding dense `[B,H,Q,K]` masks.
|
| 16 |
+
- Supports log-sum-exp merging for compatible non-packed multi-segment plans.
|
| 17 |
+
|
| 18 |
+
## Attention Types
|
| 19 |
+
|
| 20 |
+
The release path intentionally supports only FlashAttention-compatible plan
|
| 21 |
+
types:
|
| 22 |
+
|
| 23 |
+
| Value | Meaning |
|
| 24 |
+
| --- | --- |
|
| 25 |
+
| `0` | Full attention over the listed key segment or packed key segments. |
|
| 26 |
+
| `1` | Bottom-right causal attention. |
|
| 27 |
+
|
| 28 |
+
The old local CUDA-only encodings (`100 + block_size` and `10000 + ...`) were
|
| 29 |
+
removed from the release.
|
| 30 |
+
|
| 31 |
+
## Runtime Knobs
|
| 32 |
+
|
| 33 |
+
| Variable | Default | Meaning |
|
| 34 |
+
| --- | --- | --- |
|
| 35 |
+
| `LA_FLASH_ATTN` | `sdpa` | Set to `la_flash` to enable this backend through `batch_utils`. |
|
| 36 |
+
| `LA_FLASH_FASTPATH` | `auto` | Use FlashAttention varlen for packed simple plans. |
|
| 37 |
+
| `LA_FLASH_SEGMENT_FASTPATH` | `auto` | Use FlashAttention varlen for multi-segment sparse plans. Full segments are packed first; other compatible segments use LSE merging. |
|
| 38 |
+
| `LA_FLASH_PLAN_STATS` | `0` | Record sparse plan statistics in inference summaries. |
|
| 39 |
+
|
| 40 |
+
## Notes
|
| 41 |
+
|
| 42 |
+
Dense prefill and stock worker-style generation should keep
|
| 43 |
+
`LA_FLASH_DENSE_BACKEND=sdpa`; LA Flash is used for sparse range plans
|
| 44 |
+
produced by `batch_utils`.
|
| 45 |
+
|
| 46 |
+
This package is for inference and evaluation. Training remains on the
|
| 47 |
+
MagiAttention backend; the batched sparse-plan decode runtime does not support
|
| 48 |
+
the `labels` training path.
|
| 49 |
+
|
| 50 |
+
## Source Layout
|
| 51 |
+
|
| 52 |
+
- `range_attention.py`: FlashAttention varlen dispatch, sparse KV packing, LSE
|
| 53 |
+
merge fallback, and availability checks.
|
kernel_utils/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FlashAttention sparse range utilities for LocateAnything batch inference."""
|
| 2 |
+
|
| 3 |
+
from .range_attention import range_attention, is_available
|
| 4 |
+
|
| 5 |
+
__all__ = ["range_attention", "is_available"]
|
kernel_utils/range_attention.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Sparse LocateAnything attention implemented with FlashAttention varlen.
|
| 2 |
+
|
| 3 |
+
The public API accepts flattened query/key/value tensors:
|
| 4 |
+
|
| 5 |
+
q: [total_q, num_q_heads, head_dim]
|
| 6 |
+
k: [total_k, num_kv_heads, head_dim]
|
| 7 |
+
v: [total_k, num_kv_heads, head_dim]
|
| 8 |
+
|
| 9 |
+
and a Magi-style range plan:
|
| 10 |
+
|
| 11 |
+
q_ranges: [num_ranges, 2]
|
| 12 |
+
k_ranges: [num_key_segments, 2]
|
| 13 |
+
segment_offsets: [num_query_groups + 1]
|
| 14 |
+
attn_type_map:
|
| 15 |
+
0 = full attention over the listed key segment(s)
|
| 16 |
+
1 = bottom-right causal attention
|
| 17 |
+
|
| 18 |
+
For LocateAnything hybrid MTP decode, batch_utils represents the window as a
|
| 19 |
+
causal prefix plus full-attention sparse window segments. This module packs
|
| 20 |
+
those visible KV segments and calls FlashAttention varlen, avoiding dense masks.
|
| 21 |
+
"""
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
|
| 24 |
+
import os
|
| 25 |
+
from typing import Optional
|
| 26 |
+
|
| 27 |
+
import torch
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
_FLASH_ATTN_VARLEN = None
|
| 31 |
+
_FLASH_ATTN_ERROR: Optional[BaseException] = None
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _env_enabled(name: str, default: str = "auto") -> bool:
|
| 35 |
+
value = os.environ.get(name, default).strip().lower()
|
| 36 |
+
return value in {"", "auto", "1", "on", "true", "yes", "force"}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def is_available() -> bool:
|
| 40 |
+
try:
|
| 41 |
+
_load_flash_attn_varlen()
|
| 42 |
+
return True
|
| 43 |
+
except Exception:
|
| 44 |
+
return False
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _flash_fastpath_enabled() -> bool:
|
| 48 |
+
return _env_enabled("LA_FLASH_FASTPATH", "auto")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _flash_segment_fastpath_enabled() -> bool:
|
| 52 |
+
return _env_enabled("LA_FLASH_SEGMENT_FASTPATH", "auto")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _load_flash_attn_varlen():
|
| 56 |
+
global _FLASH_ATTN_VARLEN, _FLASH_ATTN_ERROR
|
| 57 |
+
if _FLASH_ATTN_VARLEN is not None:
|
| 58 |
+
return _FLASH_ATTN_VARLEN
|
| 59 |
+
if _FLASH_ATTN_ERROR is not None:
|
| 60 |
+
raise _FLASH_ATTN_ERROR
|
| 61 |
+
try:
|
| 62 |
+
from flash_attn import flash_attn_varlen_func
|
| 63 |
+
|
| 64 |
+
_FLASH_ATTN_VARLEN = flash_attn_varlen_func
|
| 65 |
+
return _FLASH_ATTN_VARLEN
|
| 66 |
+
except BaseException as exc:
|
| 67 |
+
_FLASH_ATTN_ERROR = exc
|
| 68 |
+
raise
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _coalesce_query_groups(q_ranges, k_ranges, attn_type_map):
|
| 72 |
+
"""Group consecutive entries that share the same query span and mask type."""
|
| 73 |
+
if q_ranges.numel() == 0:
|
| 74 |
+
segment_offsets = torch.zeros((1,), dtype=torch.int32, device=q_ranges.device)
|
| 75 |
+
return q_ranges, k_ranges, segment_offsets, attn_type_map, 0, 0
|
| 76 |
+
|
| 77 |
+
q_cpu = q_ranges.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 78 |
+
t_cpu = attn_type_map.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 79 |
+
grouped_q = []
|
| 80 |
+
grouped_t = []
|
| 81 |
+
offsets = [0]
|
| 82 |
+
max_q_len = 0
|
| 83 |
+
last_q = None
|
| 84 |
+
last_t = None
|
| 85 |
+
for idx, (qr, attn_type) in enumerate(zip(q_cpu.tolist(), t_cpu.tolist())):
|
| 86 |
+
key = (int(qr[0]), int(qr[1]))
|
| 87 |
+
attn_type = int(attn_type)
|
| 88 |
+
if attn_type not in (0, 1):
|
| 89 |
+
raise RuntimeError(
|
| 90 |
+
"LA Flash path only supports attn_type 0/1. "
|
| 91 |
+
f"Got attn_type={attn_type}; regenerate the plan without the old local kernel encodings."
|
| 92 |
+
)
|
| 93 |
+
if last_q is None:
|
| 94 |
+
grouped_q.append([key[0], key[1]])
|
| 95 |
+
grouped_t.append(attn_type)
|
| 96 |
+
max_q_len = max(max_q_len, key[1] - key[0])
|
| 97 |
+
last_q = key
|
| 98 |
+
last_t = attn_type
|
| 99 |
+
continue
|
| 100 |
+
if key == last_q and attn_type == last_t:
|
| 101 |
+
continue
|
| 102 |
+
offsets.append(idx)
|
| 103 |
+
grouped_q.append([key[0], key[1]])
|
| 104 |
+
grouped_t.append(attn_type)
|
| 105 |
+
max_q_len = max(max_q_len, key[1] - key[0])
|
| 106 |
+
last_q = key
|
| 107 |
+
last_t = attn_type
|
| 108 |
+
offsets.append(int(q_ranges.shape[0]))
|
| 109 |
+
|
| 110 |
+
k_cpu = k_ranges.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 111 |
+
max_k_len = max((int(end) - int(start) for start, end in k_cpu.tolist()), default=0)
|
| 112 |
+
|
| 113 |
+
return (
|
| 114 |
+
torch.tensor(grouped_q, dtype=torch.int32, device=q_ranges.device).contiguous(),
|
| 115 |
+
k_ranges,
|
| 116 |
+
torch.tensor(offsets, dtype=torch.int32, device=q_ranges.device).contiguous(),
|
| 117 |
+
torch.tensor(grouped_t, dtype=torch.int32, device=q_ranges.device).contiguous(),
|
| 118 |
+
int(max_q_len),
|
| 119 |
+
int(max_k_len),
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _flash_lse_to_tq_h(lse, total_q, q_lengths=None):
|
| 124 |
+
if lse is None:
|
| 125 |
+
return None
|
| 126 |
+
if lse.dim() != 2:
|
| 127 |
+
if lse.dim() == 3 and q_lengths is not None and lse.shape[0] == len(q_lengths):
|
| 128 |
+
chunks = []
|
| 129 |
+
for idx, q_len in enumerate(q_lengths):
|
| 130 |
+
q_len = int(q_len)
|
| 131 |
+
if lse.shape[1] == 0 or q_len > lse.shape[2]:
|
| 132 |
+
return None
|
| 133 |
+
chunks.append(lse[idx, :, :q_len].transpose(0, 1).contiguous())
|
| 134 |
+
merged = torch.cat(chunks, dim=0).float()
|
| 135 |
+
return merged if merged.shape[0] == total_q else None
|
| 136 |
+
return None
|
| 137 |
+
if lse.shape[0] == total_q:
|
| 138 |
+
return lse.float()
|
| 139 |
+
if lse.shape[1] == total_q:
|
| 140 |
+
return lse.transpose(0, 1).contiguous().float()
|
| 141 |
+
return None
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def _make_cu_seqlens(lengths, device):
|
| 145 |
+
return torch.tensor([0] + list(torch.tensor(lengths).cumsum(0).tolist()), device=device, dtype=torch.int32)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def _try_flash_segment_merge(
|
| 149 |
+
q,
|
| 150 |
+
k,
|
| 151 |
+
v,
|
| 152 |
+
k_ranges,
|
| 153 |
+
segment_offsets,
|
| 154 |
+
group_q_ranges,
|
| 155 |
+
group_attn_type_map,
|
| 156 |
+
softmax_scale,
|
| 157 |
+
):
|
| 158 |
+
if not _flash_segment_fastpath_enabled():
|
| 159 |
+
return None
|
| 160 |
+
if q.dtype not in (torch.float16, torch.bfloat16) or k.dtype != q.dtype or v.dtype != q.dtype:
|
| 161 |
+
return None
|
| 162 |
+
if group_q_ranges is None or segment_offsets is None or group_attn_type_map is None:
|
| 163 |
+
return None
|
| 164 |
+
|
| 165 |
+
flash_attn_varlen = _load_flash_attn_varlen()
|
| 166 |
+
gq_cpu = group_q_ranges.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 167 |
+
kr_cpu = k_ranges.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 168 |
+
seg_cpu = segment_offsets.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 169 |
+
type_cpu = group_attn_type_map.detach().to(device="cpu", dtype=torch.int32).contiguous()
|
| 170 |
+
|
| 171 |
+
groups = []
|
| 172 |
+
max_segments = 0
|
| 173 |
+
for group_idx, (q_start, q_end) in enumerate(gq_cpu.tolist()):
|
| 174 |
+
attn_type = int(type_cpu[group_idx].item())
|
| 175 |
+
if attn_type not in (0, 1):
|
| 176 |
+
return None
|
| 177 |
+
seg_start = int(seg_cpu[group_idx].item())
|
| 178 |
+
seg_end = int(seg_cpu[group_idx + 1].item())
|
| 179 |
+
if seg_end <= seg_start or q_end <= q_start:
|
| 180 |
+
return None
|
| 181 |
+
segments = kr_cpu[seg_start:seg_end].tolist()
|
| 182 |
+
max_segments = max(max_segments, len(segments))
|
| 183 |
+
groups.append((int(q_start), int(q_end), attn_type, [(int(a), int(b)) for a, b in segments]))
|
| 184 |
+
|
| 185 |
+
if not groups or max_segments == 0:
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
+
can_pack_full_groups = all(attn_type == 0 or len(segments) == 1 for _, _, attn_type, segments in groups)
|
| 189 |
+
if can_pack_full_groups:
|
| 190 |
+
merged = torch.empty((q.shape[0], q.shape[1], q.shape[2]), device=q.device, dtype=q.dtype)
|
| 191 |
+
covered = torch.zeros((q.shape[0],), device=q.device, dtype=torch.bool)
|
| 192 |
+
for attn_type in (0, 1):
|
| 193 |
+
q_slices = []
|
| 194 |
+
k_slices = []
|
| 195 |
+
v_slices = []
|
| 196 |
+
q_lengths = []
|
| 197 |
+
k_lengths = []
|
| 198 |
+
targets = []
|
| 199 |
+
for q_start, q_end, group_type, segments in groups:
|
| 200 |
+
if group_type != attn_type:
|
| 201 |
+
continue
|
| 202 |
+
q_slices.append(q[q_start:q_end])
|
| 203 |
+
if attn_type == 0 and len(segments) > 1:
|
| 204 |
+
k_slices.append(torch.cat([k[start:end] for start, end in segments], dim=0))
|
| 205 |
+
v_slices.append(torch.cat([v[start:end] for start, end in segments], dim=0))
|
| 206 |
+
k_lengths.append(sum(end - start for start, end in segments))
|
| 207 |
+
else:
|
| 208 |
+
k_start, k_end = segments[0]
|
| 209 |
+
k_slices.append(k[k_start:k_end])
|
| 210 |
+
v_slices.append(v[k_start:k_end])
|
| 211 |
+
k_lengths.append(k_end - k_start)
|
| 212 |
+
q_lengths.append(q_end - q_start)
|
| 213 |
+
targets.append((q_start, q_end))
|
| 214 |
+
if not q_slices:
|
| 215 |
+
continue
|
| 216 |
+
|
| 217 |
+
out_pass = flash_attn_varlen(
|
| 218 |
+
torch.cat(q_slices, dim=0).contiguous(),
|
| 219 |
+
torch.cat(k_slices, dim=0).contiguous(),
|
| 220 |
+
torch.cat(v_slices, dim=0).contiguous(),
|
| 221 |
+
_make_cu_seqlens(q_lengths, q.device),
|
| 222 |
+
_make_cu_seqlens(k_lengths, q.device),
|
| 223 |
+
int(max(q_lengths)),
|
| 224 |
+
int(max(k_lengths)),
|
| 225 |
+
dropout_p=0.0,
|
| 226 |
+
softmax_scale=float(softmax_scale),
|
| 227 |
+
causal=bool(attn_type == 1),
|
| 228 |
+
)
|
| 229 |
+
if isinstance(out_pass, tuple):
|
| 230 |
+
out_pass = out_pass[0]
|
| 231 |
+
|
| 232 |
+
cursor = 0
|
| 233 |
+
for q_start, q_end in targets:
|
| 234 |
+
q_len = q_end - q_start
|
| 235 |
+
merged[q_start:q_end] = out_pass[cursor:cursor + q_len]
|
| 236 |
+
covered[q_start:q_end] = True
|
| 237 |
+
cursor += q_len
|
| 238 |
+
|
| 239 |
+
if bool(covered.all().item()):
|
| 240 |
+
return merged
|
| 241 |
+
|
| 242 |
+
merged = torch.zeros((q.shape[0], q.shape[1], q.shape[2]), device=q.device, dtype=torch.float32)
|
| 243 |
+
merged_lse = torch.full((q.shape[0], q.shape[1]), -float("inf"), device=q.device, dtype=torch.float32)
|
| 244 |
+
covered = torch.zeros((q.shape[0],), device=q.device, dtype=torch.bool)
|
| 245 |
+
|
| 246 |
+
for segment_idx in range(max_segments):
|
| 247 |
+
for attn_type in (0, 1):
|
| 248 |
+
q_slices = []
|
| 249 |
+
k_slices = []
|
| 250 |
+
v_slices = []
|
| 251 |
+
q_lengths = []
|
| 252 |
+
k_lengths = []
|
| 253 |
+
targets = []
|
| 254 |
+
for q_start, q_end, group_type, segments in groups:
|
| 255 |
+
if group_type != attn_type or segment_idx >= len(segments):
|
| 256 |
+
continue
|
| 257 |
+
k_start, k_end = segments[segment_idx]
|
| 258 |
+
if k_end <= k_start:
|
| 259 |
+
continue
|
| 260 |
+
q_slices.append(q[q_start:q_end])
|
| 261 |
+
k_slices.append(k[k_start:k_end])
|
| 262 |
+
v_slices.append(v[k_start:k_end])
|
| 263 |
+
q_lengths.append(q_end - q_start)
|
| 264 |
+
k_lengths.append(k_end - k_start)
|
| 265 |
+
targets.append((q_start, q_end))
|
| 266 |
+
if not q_slices:
|
| 267 |
+
continue
|
| 268 |
+
|
| 269 |
+
result = flash_attn_varlen(
|
| 270 |
+
torch.cat(q_slices, dim=0).contiguous(),
|
| 271 |
+
torch.cat(k_slices, dim=0).contiguous(),
|
| 272 |
+
torch.cat(v_slices, dim=0).contiguous(),
|
| 273 |
+
_make_cu_seqlens(q_lengths, q.device),
|
| 274 |
+
_make_cu_seqlens(k_lengths, q.device),
|
| 275 |
+
int(max(q_lengths)),
|
| 276 |
+
int(max(k_lengths)),
|
| 277 |
+
dropout_p=0.0,
|
| 278 |
+
softmax_scale=float(softmax_scale),
|
| 279 |
+
causal=bool(attn_type == 1),
|
| 280 |
+
return_attn_probs=True,
|
| 281 |
+
)
|
| 282 |
+
if not isinstance(result, tuple) or len(result) < 2:
|
| 283 |
+
return None
|
| 284 |
+
out_pass = result[0]
|
| 285 |
+
lse_pass = _flash_lse_to_tq_h(result[1], out_pass.shape[0], q_lengths)
|
| 286 |
+
if lse_pass is None:
|
| 287 |
+
return None
|
| 288 |
+
|
| 289 |
+
cursor = 0
|
| 290 |
+
for q_start, q_end in targets:
|
| 291 |
+
q_len = q_end - q_start
|
| 292 |
+
out_seg = out_pass[cursor:cursor + q_len].float()
|
| 293 |
+
lse_seg = lse_pass[cursor:cursor + q_len]
|
| 294 |
+
old_lse = merged_lse[q_start:q_end]
|
| 295 |
+
new_lse = torch.maximum(old_lse, lse_seg)
|
| 296 |
+
old_w = torch.exp(old_lse - new_lse)
|
| 297 |
+
seg_w = torch.exp(lse_seg - new_lse)
|
| 298 |
+
denom = (old_w + seg_w).clamp_min(1e-20)
|
| 299 |
+
merged[q_start:q_end] = (
|
| 300 |
+
merged[q_start:q_end] * old_w.unsqueeze(-1)
|
| 301 |
+
+ out_seg * seg_w.unsqueeze(-1)
|
| 302 |
+
) / denom.unsqueeze(-1)
|
| 303 |
+
merged_lse[q_start:q_end] = new_lse + torch.log(denom)
|
| 304 |
+
covered[q_start:q_end] = True
|
| 305 |
+
cursor += q_len
|
| 306 |
+
|
| 307 |
+
if not bool(covered.all().item()):
|
| 308 |
+
return None
|
| 309 |
+
return merged.to(dtype=q.dtype)
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def range_attention(
|
| 313 |
+
q,
|
| 314 |
+
k,
|
| 315 |
+
v,
|
| 316 |
+
q_ranges,
|
| 317 |
+
k_ranges,
|
| 318 |
+
attn_type_map,
|
| 319 |
+
softmax_scale: float,
|
| 320 |
+
*,
|
| 321 |
+
segment_offsets=None,
|
| 322 |
+
group_q_ranges=None,
|
| 323 |
+
group_attn_type_map=None,
|
| 324 |
+
max_q_len=None,
|
| 325 |
+
max_k_len=None,
|
| 326 |
+
flash_cu_seqlens_q=None,
|
| 327 |
+
flash_cu_seqlens_k=None,
|
| 328 |
+
flash_causal=None,
|
| 329 |
+
disjoint_q_ranges=None,
|
| 330 |
+
):
|
| 331 |
+
"""Run sparse range attention through FlashAttention varlen."""
|
| 332 |
+
del disjoint_q_ranges
|
| 333 |
+
if not q.is_cuda:
|
| 334 |
+
raise RuntimeError("LA Flash range_attention requires CUDA tensors")
|
| 335 |
+
if segment_offsets is None or group_q_ranges is None or group_attn_type_map is None:
|
| 336 |
+
(
|
| 337 |
+
group_q_ranges,
|
| 338 |
+
k_ranges,
|
| 339 |
+
segment_offsets,
|
| 340 |
+
group_attn_type_map,
|
| 341 |
+
computed_max_q_len,
|
| 342 |
+
computed_max_k_len,
|
| 343 |
+
) = _coalesce_query_groups(q_ranges, k_ranges, attn_type_map)
|
| 344 |
+
if max_q_len is None:
|
| 345 |
+
max_q_len = computed_max_q_len
|
| 346 |
+
if max_k_len is None:
|
| 347 |
+
max_k_len = computed_max_k_len
|
| 348 |
+
elif max_q_len is None:
|
| 349 |
+
lengths = (group_q_ranges[:, 1] - group_q_ranges[:, 0]).detach().to(device="cpu")
|
| 350 |
+
max_q_len = int(lengths.max().item()) if lengths.numel() else 0
|
| 351 |
+
if max_k_len is None:
|
| 352 |
+
k_lengths = (k_ranges[:, 1] - k_ranges[:, 0]).detach().to(device="cpu")
|
| 353 |
+
max_k_len = int(k_lengths.max().item()) if k_lengths.numel() else 0
|
| 354 |
+
|
| 355 |
+
if (
|
| 356 |
+
flash_cu_seqlens_q is not None
|
| 357 |
+
and flash_cu_seqlens_k is not None
|
| 358 |
+
and flash_causal is not None
|
| 359 |
+
and _flash_fastpath_enabled()
|
| 360 |
+
and q.dtype in (torch.float16, torch.bfloat16)
|
| 361 |
+
and k.dtype == q.dtype
|
| 362 |
+
and v.dtype == q.dtype
|
| 363 |
+
):
|
| 364 |
+
flash_attn_varlen = _load_flash_attn_varlen()
|
| 365 |
+
return flash_attn_varlen(
|
| 366 |
+
q.contiguous(),
|
| 367 |
+
k.contiguous(),
|
| 368 |
+
v.contiguous(),
|
| 369 |
+
flash_cu_seqlens_q.contiguous().to(device=q.device, dtype=torch.int32),
|
| 370 |
+
flash_cu_seqlens_k.contiguous().to(device=q.device, dtype=torch.int32),
|
| 371 |
+
int(max_q_len),
|
| 372 |
+
int(max_k_len),
|
| 373 |
+
dropout_p=0.0,
|
| 374 |
+
softmax_scale=float(softmax_scale),
|
| 375 |
+
causal=bool(flash_causal),
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
segment_out = _try_flash_segment_merge(
|
| 379 |
+
q,
|
| 380 |
+
k,
|
| 381 |
+
v,
|
| 382 |
+
k_ranges,
|
| 383 |
+
segment_offsets,
|
| 384 |
+
group_q_ranges,
|
| 385 |
+
group_attn_type_map,
|
| 386 |
+
softmax_scale,
|
| 387 |
+
)
|
| 388 |
+
if segment_out is not None:
|
| 389 |
+
return segment_out
|
| 390 |
+
|
| 391 |
+
raise RuntimeError(
|
| 392 |
+
"LA Flash could not express this range plan with FlashAttention varlen. "
|
| 393 |
+
"Only attn_type 0/1 range plans are supported in the release path."
|
| 394 |
+
)
|
pyproject.toml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=68", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "locateanything-la-flash"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "LocateAnything batch utils with LA Flash inference backend."
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"numpy",
|
| 12 |
+
"pillow",
|
| 13 |
+
"torch",
|
| 14 |
+
"transformers",
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
[tool.setuptools]
|
| 18 |
+
packages = ["batch_utils", "kernel_utils"]
|