惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
The Hacker News
The Hacker News
O
OpenAI News
TaoSecurity Blog
TaoSecurity Blog
Google DeepMind News
Google DeepMind News
Forbes - Security
Forbes - Security
Spread Privacy
Spread Privacy
SecWiki News
SecWiki News
V
Vulnerabilities – Threatpost
Latest news
Latest news
Y
Y Combinator Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
Cloudbric
Cloudbric
Webroot Blog
Webroot Blog
G
Google Developers Blog
M
MIT News - Artificial intelligence
Cisco Talos Blog
Cisco Talos Blog
Blog — PlanetScale
Blog — PlanetScale
Attack and Defense Labs
Attack and Defense Labs
aimingoo的专栏
aimingoo的专栏
The Register - Security
The Register - Security
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
L
LINUX DO - 热门话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
美团技术团队
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
有赞技术团队
有赞技术团队
N
Netflix TechBlog - Medium
H
Heimdal Security Blog
L
Lohrmann on Cybersecurity
The Last Watchdog
The Last Watchdog
MyScale Blog
MyScale Blog
C
CERT Recently Published Vulnerability Notes
Hugging Face - Blog
Hugging Face - Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
A
About on SuperTechFans
博客园 - 叶小钗
博客园_首页
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Fortinet All Blogs
博客园 - 聂微东

StudyingLover's Blog

Diffusion Policy笔记 rwkv笔记 act笔记 opencode多智能体 nanobot-pre-train nanobot-rl nanobot-sft nanobot-checkpoint_manager nanobot-gpt nanobot-mid-train Vision Mamba (Vim)笔记 BPE演示 最后一遍学习Transformer YOLOv5 目标检测笔记 下载根服务器解析记录 Dynaseal A Backend-Controlled LLM API Key Distribution Scheme with Constrained Invocation Parameters 判断链表有环 王道25数据结构勘误 关于perplexity的open-sourcing-r1-1776 AI为什么不像人类一样进行多轮对话 新博客改造日记和功能测试 linuxqq只显示登陆背景图 数字设计和计算机体系结构(机械工业出版社)勘误(自制) Dynaseal:面向未来端侧llm agent的llm api key分发机制 A Definitive Guide to Markdown Style This post is using MDX, Where you can embed JSX and Astro components RT-Patch学习 pydantic实现的LLM ReAct fastapi 和 uvicorn 设置监听 ipv6 pydantic+openai+json 控制大模型输出的最佳范式 解决 Matplotlib Scatter 不支持 Marker 列表的问题:mscatter 实现 roofline model zhipuAI接口兼容openai 在docker部署fastapi宝塔里使用nginx反代套上cloudflare获取请求的真实ip clion搭建libbpf-bootstrap开发环境 coze+coze-discord-proxy+ChatNextWebUI实现AI自由 安卓内核时间使用的是UTC时间 colab运行google最新开源模型Gemma Sora技术报告 视频生成模型作为世界模拟器 笔记 archlinux flutter开发踩坑 fastapi集成google auth登录 linux下NTFS磁盘报错输入输出错误 Venn-Abers 预测器 基于Venn-Abers预测器的系统日志异常检测方法_顾兆军 手机平板远程访问kvm虚拟机的windows phi-2弱智吧测评 poe的gemini pro或是百度开发 google gemini api使用 google gemini api申请 构建用于复杂数据处理的高效UDP服务器和客户端 matplotlib中文字体渲染 TruFor笔记和代码复现 深入分析:GitHub Trending 项目 "multipleWindow3dScene" pua大模型 ggml教程|mnist手写体识别量化推理 xgboost2.0最佳实践 xgboost使用GPU最佳实践 马踏棋盘 cloudlflare推理llama2 docker搭建elasticsearch并使用python连接 FreeU-文字生成图片的免费午餐笔记 使用xgboost的c接口推理模型 Archlinux使用CMake调用xgboost的c接口 m2cgen生成机器学习c语言推理代码 xgboost模型序列化存储并推理 speculative-sampling笔记 prompt2model笔记 RoboTAP笔记 自建obsidian同步服务 MediaPipe即将推出图像生成服务 Dual-Stream Diffusion Net for Text-to-Video Generation笔记 ViT在DDPM取代UNet(DiT) arch4edu搞崩了我的flutter LISA(推理分割)笔记 在终端绘制GPU显存使用曲线 GPTBot介绍 arch蓝牙无法连接 GPU部署llama-cpp-python(llama.cpp通用) 花式求GCD 使用llama构建一个蜜罐(前端) 使用llama构建一个蜜罐(后端) llama-cpp-python快速上手 快速上手llama2.c(更新版) Paper Gestalt笔记 DINO-v2笔记 快速上手llama2.c AnyDoor笔记 Archlinux安装scrcpy加载共享库出错 error while loading shared libraries:libusb-1.0.so.0:wrong ELF class:ELFCLASS32 npc_gzip笔记 python调用c++函数 Filesystem type ntfs3,ntfs not configured in kernel open_clip编码图像和文本 PicGo配置CloudflareR2图片储存 ArchlinuxGnome快捷键打开终端 clip-interrogator代码解析 GroundingDINO安装报错解决 2023华为鲲鹏畅想日暨西安高新国际会议中心零食午饭测评 RoboMaster开源仓库汇总(长期更新) 没有手都可以在腾讯云创建镜像 I3D笔记
nanovllm-block_manager
About the Author StudyingLover · 2026-01-22 · via StudyingLover's Blog

Block

先看物理块管理器,一个Block类比就是操作系统中的页表项

class Block:

    def __init__(self, block_id):
        self.block_id = block_id
        self.ref_count = 0
        self.hash = -1
        self.token_ids = []

    def update(self, hash: int, token_ids: list[int]):
        self.hash = hash
        self.token_ids = token_ids

    def reset(self):
        self.ref_count = 1
        self.hash = -1
        self.token_ids = []
  • block_id: int : 在 KV Cache Tensor 中的索引位置
  • ref_count: int : 有多少个序列在用这个块(引用计数)
  • hash: int : 这个块存储内容的哈希值(用于 Prefix Caching)
  • token_ids: list[int] : 缓存的 token 内容(仅用于验证,不是真正的 KV)

updatereset 一个是分配/更新一个是释放

BlockManager

下面的BlockManager 就是页表管理器了

  • block_size: int : 页大小(256 tokens)
  • blocks: list[Block] : 页表(所有页表项),维护每个物理块的状态
  • hash_to_block_id: dict : 内容索引(用于 Prefix Caching), 类似文件系统的 inode 缓存,实现 Prefix Caching
  • free_block_ids: deque : 空闲页链表,快速分配新块,新请求来时,直接从队首取 free_block_ids[0]
  • used_block_ids: set : 已分配页集合,快速判断块是否已分配

链式哈希

链式哈希 是一种用于 KV Cache(键值缓存)管理 的机制,主要用于实现 Prefix Caching(前缀缓存。它的核心逻辑是:一个 Block 的哈希值不仅取决于它内部的 Token,还取决于它前一个 Block 的哈希值。

==换言之,对两个内容相同的块,我只需要看一下这两个链式哈希的是否一样就知道这个序列前面的内容是否也一样==,这对我们判断能否复用Prefix Caching帮助很大

链式哈希图示

def compute_hash(cls, token_ids: list[int], prefix: int = -1):
    h = xxhash.xxh64()

    # 关键点:如果有前缀(即前一个 Block 的哈希),先把它 update 进去
    if prefix != -1:
        h.update(prefix.to_bytes(8, "little"))

    # 然后再 update 当前 Block 的 token
    h.update(np.array(token_ids).tobytes())
    return h.intdigest()

在分配时的链式传递

h = -1
for i in range(seq.num_blocks):
    token_ids = seq.block(i)
    # 上一轮循环计算出的 h,被当作这一轮的 prefix 传入
    h = (
        self.compute_hash(token_ids, h)
        if len(token_ids) == self.block_size
        else -1
    )

分配和释放

_allocate_block_deallocate_block 是分配和释放一个显存块的基本功能,can_allocate用于判断显存块还够不够用

allocate 就是具体分配的函数了,先遍历序列,给每个块分配一个显存块并且计算出他们的链式存储。接下来根据他的链式哈希值去找是否有可复用的块,根据没有或内容不匹配判断是否命中 - 未命中,分配新块 - 命中,分配新块

def allocate(self, seq: Sequence):
    assert not seq.block_table
    h = -1
    cache_miss = False
    for i in range(seq.num_blocks):
        token_ids = seq.block(i)
        h = (
            self.compute_hash(token_ids, h)
            if len(token_ids) == self.block_size
            else -1
        )
        block_id = self.hash_to_block_id.get(h, -1)
        if block_id == -1 or self.blocks[block_id].token_ids != token_ids:
            cache_miss = True
        if cache_miss:
            block_id = self.free_block_ids[0]
            block = self._allocate_block(block_id)
        else:
            seq.num_cached_tokens += self.block_size
            if block_id in self.used_block_ids:
                block = self.blocks[block_id]
                block.ref_count += 1
            else:
                block = self._allocate_block(block_id)
        if h != -1:
            block.update(h, token_ids)
            self.hash_to_block_id[h] = block_id
        seq.block_table.append(bl

这里有个问题啊,命中了缓存时,有个判断是在不在used_block_ids?不在的话还要分配。这是因为deallocate没释放过hash_to_block_id,也就是说hash_to_block_id永久保存,但块本身可能被释放回收了,所以分配的时候要处理。

deallocate是释放块的逻辑,没什么好说的

def deallocate(self, seq: Sequence):
    for block_id in reversed(seq.block_table):
        block = self.blocks[block_id]
        block.ref_count -= 1
        if block.ref_count == 0:
            self._deallocate_block(block_id)
    seq.num_cached_tokens = 0
    seq.block_table.clear()

还有两个在decode阶段做的

can_append判断能否为序列追加新的token(在decode阶段每生成一个token后调用)

may_append在块满或新块做必要的操作,其他时间静默