资讯动态

llama.cpp与GGUF:本地部署大模型的推理引擎与量化选型指南

发布时间:2026/10/16 4:53:08 来源:尧图企业网站定制
A同学最近想把一个 7B 参数的开源模型在本地跑起来那台机器只有 8GB 内存、一块老显卡。折腾一下午之后他跟我说“模型我下载了然后呢”这个问题我太熟悉了。很多人在本地玩大模型卡住的地方不是“找不到模型”而是“模型下下来之后不知道该用什么把它跑起来”。llama.cpp 和 GGUF 就是这最后一公里的答案准确说是两个配合紧密的答案llama.cpp 是一个用 C/C 写的推理引擎GGUF 是它可以直接加载的模型封装格式。我习惯把这两个合在一起叫“裸引擎”——它不带界面、不带花哨功能就是一个能让你在本地机器上把大模型真正跑起来的最小组件。这篇文章我会把我自己在这一套组合上的理解、选型经验、实操步骤和踩坑过程全部拆开讲适合那些已经把模型下载到硬盘上、但还没跑起来的人也适合想搞清楚量化档位怎么选、上下文为什么卡的人。看完你应该能自己完成从编译到调用 API 的全流程并且知道每个关键参数为什么要那样设置。1. “裸引擎”到底是什么llama.cpp 和 GGUF 各自扮演什么角色先说概念。llama.cpp 是一个推理引擎项目它把大模型的推理逻辑也就是“给你一句话让模型继续生成下一句”的过程全部用 C/C 重新实现了一遍不依赖大型深度学习框架。GGUF 则是一个模型文件封装格式它把模型的权重、分词器、结构参数和各类元信息打包在一个文件里。两者合在一起就构成了一个可以独立运行的最小闭环GGUF 文件是“发动机图纸燃料”llama.cpp 是“发动机本体”。这个比喻我觉得很贴切。模型的权重文件比如 PyTorch 格式的那一堆 .bin、.safetensors相当于一堆散落的零件不是拿过来就能用的GGUF 则是把这些零件按照统一标准组装好的成品llama.cpp 拿到这个成品就能直接驱动。为什么这套组合会被大量本地部署玩家选用核心原因就是四个字简化依赖。1.1 本地推理的真正门槛在于依赖链大部分人在本地跑大模型第一次都尝试用 Python 生态来加载比如写几行代码调 pyTorch、Transformers 之类的库然后把模型加载进来。这条路的问题在于光装依赖就能装出几十上百个包有些包还有版本冲突模型权重动辄几个 GB加载到内存后还要再做一遍格式解析更不用说很多工具库默认按照训练框架的思路设计推理只是附带的。我帮 A同学装环境的时候光是解决其中一个库的版本冲突就花了快一个小时而真正加载模型之后内存已经被撑到极限生成一句话的速度让人怀疑电脑是不是死机了。llama.cpp 用的思路完全不同。它不依赖 Python不依赖庞大的深度学习框架甚至不需要显卡也能跑纯 CPU 推理。整个项目编译出来就是几个可执行文件你拿着模型文件直接跑就行。这里的“裸”不是贬义而是指它没有乱七八糟的中间层启动快、加载快、资源占用相对干净。1.2 引擎和模型文件必须匹配别混搭有一点必须强调llama.cpp 不是“什么模型都能加载”它专门为 GGUF 这种格式做了深度支持。虽然它也能通过转换脚本把别的格式转成 GGUF但运行时最舒服的组合就是 GGUF 文件 llama.cpp 引擎。这个匹配关系决定了你从模型社区下载文件的时候应该优先找 GGUF 版本而不是原始的 safetensors 版本。GGUF 格式在文件中就包含了分词器信息这意味着引擎加载时不需要额外指定“这个模型用什么分词方式”格式和引擎之间是自洽的。很多刚上手的人搞混这一点下载了一个 PyTorch 权重文件夹然后用 llama.cpp 去加载自然是一堆报错。所以第一步其实是改变一下获取模型的方式找 GGUF 后缀的文件别看到“.bin”就下。1.3 一个“跑得起来”的最小闭环需要什么要说清楚最小闭环我把它拆成三块模型文件、推理引擎、硬件资源。模型文件是一份 GGUF大小取决于模型参数量和量化方式推理引擎是编译好的 llama.cpp 可执行文件硬件资源就是内存和 CPU/GPU。三者的关系是GGUF 模型大小 推理时的临时缓存不能超过内存上限引擎的编译选项要匹配你的硬件有无 GPU什么加速接口。只要满足这些一个能跑的最小闭环就成立了。很多人以为“本地跑大模型”需要很专业的设备其实一台 16GB 内存的普通电脑就能跑 7B 级别的量化模型8GB 内存也能跑只是上下文和速度要妥协一点。所以别被“大模型”三个字吓住它只是一个占用空间较大的程序。2. GGUF 内部结构拆解一个文件怎么把“模型”装进去我最早接触 GGUF 的时候以为它就是把原来的模型权重换了个扩展名。后来为了排查一次加载报错去翻了一下它的格式说明才发现这个文件格式的设计比我想象的讲究得多。搞清楚 GGUF 的内部结构对理解它现在这么流行的原因很有帮助。2.1 文件布局头信息、元数据、张量数据GGUF 是一个二进制的、紧凑顺序存储的格式。文件开头是固定的魔数Magic Number用来标识文件类型然后是版本号接着是张量数量、元数据键值对数量这些基本信息。元数据之后就是具体的内容区先是分词器相关的字符串表再是各个张量也就是权重矩阵的二进制数据最后是模型运算需要的一些补充信息。听起来有点抽象我换一种方式说。你可以把一个 GGUF 文件想象成一个打包好的旅行箱最外层贴着标签头部元数据写明里面装了几件物品、每件物品叫什么、有多大箱子里第一层是“使用说明”和“翻译字典”分词器和特殊 token再往下整整齐齐码着各种形状的积木权重张量每个积木上都标注了它的形状和数据类型。引擎读取文件时先快速扫一遍标签知道里面有什么然后按需把积木搬出来用而不是把所有东西一股脑倒在地上再慢慢找。这个结构的优势在于格式全部数字化没有像 JSON 那样的文本解析开销所有内容按顺序排列读取时可以连续磁盘 IO张量数据按字节对齐存放引擎可以直接把文件映射到内存中访问不需要在内存里复制一份再解析一遍。这也是为什么 GGUF 加载速度比早期格式更快的一个原因。2.2 分词器为什么必须内置在文件里这一点是我觉得 GGUF 做得最对的设计决策。大模型生成文本的过程其实是先把用户输入的文字拆成一个个 token词元然后把 token 转换成编号喂给模型生成结果之后再从编号转回文字。不同模型对同一段话怎么拆 token 的方法不一样有的模型偏好把“苹果”拆成一个 token有的模型偏好拆成两个。分词器不同模型收到的输入序列就完全不同输出质量自然千差万别。早期的一些格式把分词器信息放在模型文件之外推理时需要手动指定。一旦有人用 A 模型的分词器去加载 B 模型的权重输出的就是一堆乱码或者胡言乱语而且这种错误特别难排查因为代码没报错模型也没崩就是生成内容驴唇不对马嘴。GGUF 把分词器直接写进文件加载时引擎自动读取从根源上杜绝了这种“错配”问题。我在选模型文件时有一条简单原则同一个模型优先选 GGUF 而不是原始权重一半原因就是因为它自带分词器省去一堆手动设置的麻烦。2.3 从 GGML 到 GGUF格式演进背后的教训GGUF 不是这个项目的第一个格式它之前还有一个叫 GGML 的格式。GGML 时代这个项目就已经能跑很多模型但它的设计存在不少历史遗留问题版本兼容性差、张量信息不完整、没有统一的分词器封装。最典型的麻烦是更新版本之后旧模型文件可能无法加载开发者只能手动去查版本号、改配置。GGUF 的出现相当于一次彻底的重构把版本号管理、元数据扩展机制、数据对齐方式都重新设计了一遍并且明确规定了向后兼容策略新版本引擎能读旧版本文件旧版本引擎遇到新版本文件会给出明确的错误提示而不是崩溃。这个演进过程其实我挺有共鸣的。很多开源项目早期能做出来是因为设计足够简单但随着使用者变多各种奇怪的环境和用法涌进来那些“简单但含糊”的地方就会变成最大的坑。GGUF 的设计思路总结起来就是一句话把所有“可能因环境不同而出错”的信息尽量固化到文件内部让模型文件本身成为自解释的。这对使用者来说体验提升很明显你不需要在加载时回答一堆“这个模型用什么参数”的提问文件自己告诉你答案。2.4 加载快不快的秘密mmap 按需映射GGUF 支持的 mmap内存映射加载方式是它在启动速度上表现好的重要原因。普通加载方式是读文件时把所有内容拷进内存模型多大就占多大内存mmap 则是把文件映射到虚拟地址空间操作系统按需把实际用到的部分从磁盘读入物理内存。llama.cpp 加载一个 4GB 的 GGUF 文件用 mmap 时可能一两秒就完成映射真正开始推理时才逐步读取需要的部分。也就是说模型文件本身占用的是“虚拟内存”实际物理内存占用会在推理过程中逐渐增长但增长曲线更加平滑。这个特性带来的直接好处是你可以同时启动多个模型或者磁盘剩余空间足够但内存偏紧的机器也能跑。当然代价是要保证磁盘读取速度不要太差如果模型放在机械硬盘里首次加载时反复磁盘寻道会拖慢速度。我这里给个建议本地部署模型尽量放在 NVMe 固态硬盘上没有条件也至少要保证模型文件不在老式机械盘里不然 mmap 优势会被磁盘瓶颈吃掉一大半。3. 量化选型Q4_K_M、Q5_K_M、Q8_0 到底差在哪量化是本地大模型绕不开的话题。GGUF 文件名里常见 Q4_K_M、Q5_K_M、Q8_0 这类标记很多人直接把数字大当成“质量好”往下选最重的那个。这个思路不能说错但完全忽略了自己的硬件条件。量化选型的本质是在“文件大小、内存占用、生成质量、推理速度”这四者之间找平衡点。这一节我会把量化的机制拆开再给一套可以直接抄的选型方法。3.1 量化是什么把 16 位数字压缩成 4 位模型里面的权重本质上是一堆浮点数传统的训练结果存储精度通常是 FP1616 位半精度浮点也就是每个数字占 2 个字节。一个 7B 参数的模型用 FP16 存储光权重就是大约 14GB。量化做的事情简单说就是把这些数字从 16 位降到 4 位或 8 位降低存储精度换取更小的体积和更快的计算速度。直接粗暴地把每个 16 位数字砍成 4 位会丢失太多信息模型输出会明显变差。所以现代量化不是单独压缩每个数字而是“分组压缩”把一组数字放在一起记录这一组的公共缩放因子scale和偏移量min组内每个数字只记录它相对缩放因子的近似值。GGUF 里的 K 量化Q4_K、Q5_K 这类更进一步采用了更大的超块结构在连续多组之间再共享一个额外的缩放信息让精度损失更小。这也是为什么同样都是 4 位量化Q4_K_M 的实际效果通常好于老式的 Q4_0——因为 K 系列对权重的数值分布建模得更细致。用一个不太严谨但好懂的类比老式量化像给每个人单独量身高记录到整数厘米信息比较碎K 系列量化像先量一个班级的平均身高再让每个人记录“我和平均值的差距”公共信息共享之后整体误差就更小了。3.2 常见量化档位的真实体积和效果对比我给几个常见档位做个对比表基于 7B 参数量的模型来估算。FP16 原始大小约 14GB下面这些量化档位的文件大小是一个参考区间实际会因词典大小不同有微小浮动。档位大约文件大小相对 FP16 的质量损失适合场景FP16约 14GB基准显存/内存充足追求极致质量Q8_0约 7.3GB很小基本不可感知内存充裕、想要接近原始质量Q6_K约 5.6GB很小质量优先同时兼顾存储Q5_K_M约 4.8GB小通用推荐质量较高且体积可控Q4_K_M约 4.4GB中等部分场景可感知最常见档位内存紧张的主力选择Q3_K_M约 3.7GB较大极低内存环境只能短对话Q2_K约 3.0GB大不建议日常使用特殊情况再考虑这个表不是让你直接挑最大的。关键是看你的内存预算真 8GB 内存要跑本地模型Q4_K_M 是最平衡的选择16GB 内存就可以上 Q5_K_M 或者 Q6_K如果你跑 13B 甚至更大的模型那内存预算会紧张很多往往只能选 Q4 或更低。我的经验是不要为了追求那一点点质量提升选更大文件导致内存顶满换来的只有频繁交换内存、速度下降、体验反而更差。质量再好的模型卡到磁盘交换状态也就没质量可言了。3.3 带 imatrix 的模型文件有什么不同有些 GGUF 文件名里会带 imatrix 或 instruct、chat 这类标记。imatrix 指的是“重要性矩阵校准”importance matrix简单说就是模型作者在发布 GGUF 版本时用一批有代表性的文本数据去测试不同张量的敏感度然后针对性地优化量化策略。由于不同任务对权重的敏感度不同一个用代码语料校准过的 imatrix 版本在代码生成任务上可能比通用版本少损失一点质量用对话语料校准的版本可能更适合聊天场景。我自己的习惯是如果同一个模型有 imatrix 版本和普通版本文件大小差距又不大优先选带 imatrix 的如果模型社区标注不清楚那就选文件大小合适的普通版本不必太纠结。另外别把模型文件名里的 instruct 或 chat 和量化质量混为一谈。instruct 表示该版本针对指令跟随进行了微调chat 表示针对对话优化这些是模型本身的训练方向差异不是文件格式层面的质量差异。搞清楚这一点选型的时候就不容易盲目堆参数了。4. 本地落地实操编译、加载、调参全流程理论讲够了直接上手。我把整个流程按顺序梳理一遍从下载源码到跑起一个服务每步都会说明为什么要这么做。我默认的环境是 Linux不过思路对 Windows 同样适用区别只是编译工具链不同。4.1 编译别跳过 CMake 选项llama.cpp 提供了预编译的二进制包但我强烈建议自己编译一次。道理很简单预编译包通常是通用配置不一定启用了你硬件的加速能力。比如你的机器有支持 CUDA 的显卡预编译包可能默认不带 CUDA 支持于是推理一直在 CPU 上跑速度和 GPU 版本差好几倍。编译命令大致如下git clone llama.cpp的仓库地址 cd llama.cpp cmake -B build -DGGML_CUDAON -DCMAKE_BUILD_TYPERelease cmake --build build --config Release -j如果你用的是 AMD 显卡或者核显不想依赖 CUDA可以改成cmake -B build -DGGML_VULKANON -DCMAKE_BUILD_TYPERelease # 或者用 OpenCL cmake -B build -DGGML_OPENCLON -DCMAKE_BUILD_TYPERelease我的建议是实测两种配置先把默认 CPU 编译跑通再开 GPU 加速重新编译一次对比一下同样的提示词耗时。很多人的电脑 CPU 很强、内存也不差纯 CPU 跑 7B 模型也不是不能用但 GPU 一开速度能提升一个量级这个对比会直接改变你的使用习惯。CMake 编译完成之后build/bin 目录下会出现几个可执行文件名字通常是 llama-cli、llama-server、llama-perplexity 等。如果你是从某处下载的预编译包也要确认里面包含这几个文件别只拿到一个执行文件就以为万事大吉。4.2 用 llama-cli 跑一次完整交互准备一个 GGUF 模型文件放到某个目录后进到 build/bin 目录执行./llama-cli -m /path/to/model.gguf -p 你好介绍一下你自己 -n 256 -c 4096 --temp 0.7 --top-p 0.9 -t 4逐项说下这几个参数的含义。-m 指定模型文件路径-p 指定初始提示词-n 是最大生成 token 数量我通常先用 256 测试速度-c 是上下文大小也就是模型最多能“看到”多少历史 token--temp 是采样温度数值越高随机性越强越低越保守--top-p 是核采样参数0.9 表示只从累计概率达到 90% 的 token 里采样-t 是线程数一般设成 CPU 物理核心数的一半左右就行。注意 -c 这一个参数后面还要细讲它最容易被忽略却对内存占用影响巨大。第一次跑的时候注意观察终端里的加载日志。日志会显示模型文件信息、张量数量、量化类型、后端设备CPU 还是 GPU、加载耗时。如果日志里后端显示的是 CPU而你又希望用 GPU那就是编译配置没开对。显卡占用可以通过系统监视器查看这一步别偷懒确认加速真的生效再进入下一步。4.3 用 llama-server 把模型变成 API 服务比起命令行交互我更推荐用 llama-server 模式。它启动之后会在本地端口上提供一个 HTTP 服务你可以在别的程序里调用它也可以接各种前端工具。启动命令./llama-server -m /path/to/model.gguf -c 4096 --host 127.0.0.1 --port 8080启动后在另一个终端里用 curl 测试curl http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -d {model:local-model,messages:[{role:user,content:你好讲一个短笑话}],max_tokens:200}返回结果是一个 JSON里面 choices 数组有生成的内容。这个接口设计成了 OpenAI 兼容格式好处是很多现成的客户端工具可以直接把地址改成 http://127.0.0.1:8080/v1 就能用不需要改代码。把模型部署成 API 之后你可以用任意语言写客户端也可以把它接入到自己的工作流里比如知识库问答、批量文本分类、本地写作辅助这些场景都可以在此基础上扩展。4.4 内存预算怎么算不只是模型文件大小部署之前我强烈建议按下面的公式粗略估算内存占用总内存占用 ≈ 模型文件大小 KV 缓存 约 0.5GB 固定开销KV 缓存与上下文大小直接相关计算公式可以简化为上下文 token 数 × 层数 × 每层 KV 维度 × 2K 和 V 两份× 2 字节。拿一个常见的 7B 模型来说32 层、KV 维度约 1024上下文设 4096 时KV 缓存大约是2 × 32 × 1024 × 2 × 4096 ≈ 512MB看起来还好。但如果把上下文设成 32768这部分直接变成 4GB。这就是为什么很多人把上下文参数调大之后整个系统卡到离谱——他们没意识到 KV 缓存会吃掉比模型文件还多的内存。所以选择上下文大小一定要务实普通对话 4096 已经足够代码补全 8192 通常够用除非你真的需要处理超长文档否则别盲目追求大窗口。可以在启动服务器时给系统留一些内存余量我一般会让总占用不超过物理内存的 80%剩余作为操作系统和别的程序的开销。5. 实测跑通之后的三个坑与完整排查链路光有步骤还不够实际跑的时候一定会遇到问题。我把自己实测过程中踩过的三个典型坑放在这里每个都按“现象、排查、解决”的顺序讲你可以照着这个思路自己排查。5.1 坑一GPU 看着在跑速度却纹丝不动现象很简单我设置了 --n-gpu-layers 99心想这次 GPU 应该接管大部分层了。结果生成速度还是和纯 CPU 差不多打开任务管理器一看GPU 占用率确实上去了但时间几乎没变短。问题出在编译配置上那个可执行文件是我之前用默认配置编译的没有启用 GPU 后端。也就是说--n-gpu-layers 这个参数会被日志悄悄忽略或降到 0模型还是全部跑在 CPU 上。排查链路是这样的先看加载日志里有没有出现 GPU 加速相关的后端名称没有的话检查 CMake 配置里有没有开启对应选项如果开了注意有些老版本的 llama.cpp 需要显式设置 -DGGML_BACKEND... 之类的额外参数最后确认显卡驱动能支持对应 API。正确做法是删掉 build 目录重新编译并在启动后确认日志中出现类似“using CUDA backend”的信息。这一步很多教程会把重点放在“参数怎么调”但真正的问题往往在编译期就埋下了。请务必养成一个习惯每次更换编译配置后第一次跑模型就看加载日志确认后端类型。5.2 坑二上下文窗口设太大整个系统开始疯狂交换内存有一阵子我为了“让模型更有记忆”把 -c 参数直接设成 32768。模型确实能看到更多历史了但启动之后没聊几句系统监视器里的内存就被填满紧接着磁盘活动飙到 100%生成速度慢到让人怀疑人生。原因就是上面提到的 KV 缓存膨胀7B 模型的量化文件只有 4.4GB但 -c 32768 的 KV 缓存约 4GB再加上加载模型需要的开销轻松突破 8GB 内存操作系统不得不不断把内存页换到磁盘上。排查方式有两种一种是在启动日志里看 KV 缓存占用的估算值另一着是直接按上面的公式手算。我的调整方案很干脆把 -c 从 32768 降到 8192内存占用立刻从“顶满”变成“有余量”速度恢复正常。为这件事我专门做了一个小表格每次换模型都先算一遍上下文 4096/8192/16384 分别要多少 KV 缓存再和模型大小相加小于本机物理内存的 75% 才会选那个配置。这个习惯建议大家直接抄走。5.3 坑三回答中途变成复读机同一个句子循环输出现象模型生成到一两百字之后开始不断重复刚才已经生成的短句越重复越长最后生成出一长串完全一样的文本。第一次遇到时我以为是模型质量问题换了模型还是一样。后来逐项排查才发现是上下文被截断造成的对话历史加上新生成的内容超过了 -c 窗口大小模型往前看不到足够上下文就陷入了“找不到合适接续”的循环。另一个更隐蔽的原因是采样参数设置不合理温度太低、top-p 太小的时候模型过度保守容易反复选中同一个 token。排查顺序可以这样先恢复比较“正常”的采样值--temp 0.7 --top-p 0.9然后检查生成内容长度是否逼近上下文上限最后再考虑是不是模型本身的微调风格问题。实在不行可以把 --repeat-penalty 调大一点这个参数会对重复出现的 token 施加额外惩罚值越高模型越不容易复读。一般情况下按 temp→上下文→penalty 的顺序调一遍复读问题能解决大半。需要留意的是把上下文开太大又会导致坑二的内存问题所以追本溯源还是回到那个公式你的硬件允许多大的上下文就设置多大而不是照着模型的原始窗口能力填。5.4 把性能数据记下来比感觉可靠最后一个不算坑但我觉得值得养成的习惯是记录性能数据。llama.cpp 在每次推理结束时会在日志里打印出提示处理时间prompt eval time和生成时间eval time单位通常是 ms/token。我在调试阶段会专门记一张表写出不同上下文长度、不同线程数、有没有 GPU 加速的情况下平均每秒生成多少 token。有时候你觉得“卡了”“快了”不如看数字来得直观。比如有一次我发现生成速度从 20 token/s 掉到 7 token/s再翻记录才知道是因为另一个程序占用了大量内存导致交换频繁。如果只凭感觉可能只会盲目换个更小的模型而真实问题根本不在这。6. 裸引擎之外的思考把这套组合用熟之后你会发现它适合的场景其实比想象中广。6.1 什么时候不适合用 llama.cpp它擅长的是单机、单模型、推理优先的场景。如果你要做大规模批量推理比如一次性处理几十万条文本或者要训练、微调模型那它就不是最优选择了——训练和微调应当回到专用框架里不要指望一个推理引擎兼顾所有事情。批量推理也有例外llama-server 的并发接口稍微调一下就能一次处理一批请求小规模批量够用但吞吐量天花板肯定比专用方案低。还有一点是如果你重度依赖 Python 生态里的数据预处理和 post-processing 流程llama.cpp 的命令行管道虽然能嵌进脚本但效率不一定比直接用 Python 推理库高。选择工具要结合自己的工作流别因为“大家都在用”就硬搬。6.2 可以往哪些方向扩展基于这一套引擎最常用的扩展方向有三个。第一是本地知识库问答把文档切块、向量化、检索再把检索到的内容拼进提示词交给本地模型生成答案。这个组合可以完全离线运行数据不出机器。第二是作为统一推理后端llama-server 的 OpenAI 兼容接口可以接到各种现成的桌面客户端、编辑器插件上相当于给本机装了一个免费私有的“模型服务”。第三是嵌入式设备和边缘场景因为它是纯 C/C 实现体积小、依赖少可以交叉编译到树莓派之类的设备上做离线语音助手、特定任务分类器之类的原型。6.3 我现在的使用习惯踩过这些坑之后我形成了一个固定的工作流程拿到新硬件先算内存预算再决定用什么量化档位下载 GGUF 文件之后先用命令行做一次 30 秒的基准测试记录速度然后启动 server 模式挂到本地端口后续所有程序都走 HTTP 接口。这套流程跑顺之后本地大模型对我来说真的变成了一个可以随时调用的“裸引擎”——不在乎界面是否花哨只在乎能不能稳定可靠地把模型能力发挥出来。如果你刚开始接触这一套建议也按这个顺序来先从最小闭环跑通再逐步扩展。你会发现本地大模型的门槛没有想象中那么高无非是算清楚内存、选对量化、配好后端然后就只剩下享受自己掌握的算力了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑