【免费下载链接】Phi-3.5-mini-instruct_rai_1.8.0_hybrid
本文对Phi-3.5-mini-instruct_rai_1.8.0_hybrid(AMD Ryzen AI 混合部署的 Phi-3.5 大语言模型)进行源码逐行精讲,用通俗的语言拆解RMSNorm 层归一化、LongRoPE 长上下文位置编码、多头注意力机制三大核心模块的实现原理,并给出 config.json 关键参数速查表,帮助新手快速读懂这个 ONNX 量化模型包是怎么"跑起来"的。
📦 项目概览:这是一个什么样的模型包?
Phi-3.5-mini-instruct_rai_1.8.0_hybrid 是 AMD 基于微软 Phi-3.5-mini-instruct 模型,使用Quark 量化工具(AWQ / 分组 128 / 非对称 / UINT4 权重 / BFP16 激活)处理后得到的Ryzen AI 1.8.0 专用部署包。
所谓hybrid(混合),体现在 genai_config.json 中:ONNX Runtime GenAI 会话配置了RyzenAI执行器,并设置了hybrid_opt_max_seq_length: 4096——即以4096 token 为分界点,短序列走混合引擎优化路径,长序列仍可支持到131072(128K)上下文。这与源码中 LongRoPE 的切换阈值(见下文)正好一致。
核心文件一览表
| 文件 | 作用 |
|---|---|
| modeling_phi3.py | 模型结构源码:RMSNorm、RoPE/LongRoPE、注意力、解码器层 |
| configuration_phi3.py | Phi3Config配置类,含 LongRoPE 参数校验 |
| config.json | 架构超参数与 AWQ 量化配置 |
| genai_config.json | ONNX Runtime GenAI 推理与搜索参数 |
| rai_config.json | 各版本 Ryzen AI 允许的最大 prompt 长度 |
| model.onnx / model.onnx.data | ONNX 计算图与外部权重数据 |
| model.pb.bin | RyzenAI 引擎引用的外部权重文件 |
| tokenizer.json / chat_template.jinja | 分词器与对话模板 |
| sample_finetune.py | 基于 SFTTrainer 的微调示例脚本 |
🚀 快速上手:获取模型包
在本地克隆仓库即可得到完整模型包:
git clone https://gitcode.com/hf_mirrors/amd/Phi-3.5-mini-instruct_rai_1.8.0_hybrid配合支持 Ryzen AI 的 ONNX Runtime 环境加载 model.onnx 即可推理(量化配置见 config.json 的quantization_config)。下面进入源码本体。
🔍 源码地图:三大核心机制都在哪?
modeling_phi3.py 共 1500 多行,按数据流可以这样看:
input_ids → embed_tokens → 32 × [RMSNorm → 注意力(+RoPE) → +残差 → RMSNorm → MLP(SwiGLU) → +残差] → final RMSNorm → lm_head → logits| 机制 | 类 / 函数 | 位置 |
|---|---|---|
| RMSNorm | Phi3RMSNorm | modeling_phi3.py#L81-L95 |
| 标准 RoPE | Phi3RotaryEmbedding | modeling_phi3.py#L112-L139 |
| LongRoPE | Phi3LongRoPEScaledRotaryEmbedding | modeling_phi3.py#L142-L180 |
| 多头注意力 | Phi3Attention等三个实现 | modeling_phi3.py#L251-L381 |
| 解码器层组装 | Phi3DecoderLayer | modeling_phi3.py#L791-L866 |
🧮 逐行精讲一:RMSNorm 层归一化实现原理
Phi-3.5 没有使用传统 LayerNorm(不减均值、不用 Layer 里的 gamma/beta 双参数),而是RMSNorm——只用"均方根"来缩放激活值,计算量更小、推理更快。
核心就 3 行(modeling_phi3.py#L90-L95):
hidden_states = hidden_states.to(torch.float32) # ① 提升到 fp32 防精度损失 variance = hidden_states.pow(2).mean(-1, keepdim=True) # ② 平方后取均值(平方均值) hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon) # ③ 除以均方根 return self.weight * hidden_states.to(input_dtype) # ④ 乘可学习缩放系数 weight逐行理解:
- ① 先转 fp32:归一化在低精度下容易累积误差,先升精度、算完再转回去,是 LLM 里的通用手法。
- ②
pow(2).mean(-1):对最后一个维度(隐层维度)求平方均值,这就是 RMS(均方根)的平方,不需要像 LayerNorm 那样先减均值。 - ③
rsqrt(variance + eps):乘以均方根的倒数,等价于"除以 RMS"。eps(即variance_epsilon)防止除零,本项目取rms_norm_eps = 1e-05(见 config.json)。 - ④
self.weight:每个维度一个可学习的缩放系数(初始化为全 1),相当于 LayerNorm 的 gamma,但没有 beta(不减均值所以不需要偏置)。
它被用了几次?每个解码器层里有两次(注意力前 + MLP 前),全部 32 层结束后还有最后一次final norm(modeling_phi3.py#L1138),见 Phi3DecoderLayer。
📐 逐行精讲二:LongRoPE 长上下文位置编码实现原理
RoPE 基础:先搞懂 inv_freq 与"旋转"
RoPE(旋转位置编码)通过给 Q、K 向量"旋转"一个与位置相关的角度来编码位置信息。基础版Phi3RotaryEmbedding的关键步骤(modeling_phi3.py#L122-L139):
self.inv_freq = 1.0 / (self.base ** (arange(0, dim, 2) / dim)) # 不同维度的"旋转频率" freqs = inv_freq_expanded @ position_ids_expanded # 频率 × 位置 = 角度 cos = emb.cos(); sin = emb.sin()拿到 cos/sin 后,用rotate_half(modeling_phi3.py#L184-L188)把向量切成前后两半再旋转:
x1 = x[..., : x.shape[-1] // 2] x2 = x[..., x.shape[-1] // 2 :] return torch.cat((-x2, x1), dim=-1)最后apply_rotary_pos_emb一句q * cos + rotate_half(q) * sin就完成了对 Q 和 K 的旋转(modeling_phi3.py#L192-L216)。注意V 不旋转,只有 Q、K 需要位置信息。
LongRoPE:4096 是分水岭,长短序列用两套系数
Phi-3.5 支持 128K 上下文,但原始训练长度只有4096(original_max_position_embeddings)。直接外推会"幻觉",LongRoPE 的解法是给每个频率维度单独乘一个扩展系数,且短/长序列各一套:
if seq_len > self.original_max_position_embeddings: # 超过 4096 → 长序列模式 ext_factors = torch.tensor(self.long_factor, ...) else: # 4096 以内 → 短序列模式 ext_factors = torch.tensor(self.short_factor, ...) self.inv_freq = 1.0 / (ext_factors * self.base ** inv_freq_shape)(modeling_phi3.py#L151-L159)
这两个系数表就在 config.json 的rope_scaling里,各48 个数值——因为hidden_size 3072 ÷ 32 头 = 96个头维度,再除以 2 就是 48。规律很直观:短序列系数从 1.0 缓慢涨到约 2.8;长序列系数则一路涨到64,把低频维度"压慢"旋转,从而容纳更长序列。
另外源码还做了一个注意力 logits 校准(modeling_phi3.py#L172-L179):外推时给 cos/sin 乘一个scaling_factor(基于 131072/4096 的对数比值开根号),缓解长序列下注意力分布变平的问题。
还有一个容易被忽略的巧思:Phi3ForCausalLM.prepare_inputs_for_generation(modeling_phi3.py#L1285-L1293)会在刚越过 4096 那个 token 时主动丢弃 KV Cache 重算一次——因为跨越分界点意味着全部换用 long_factor,旧缓存的 K 值旋转角度已不匹配。宁可慢一个 token,也要保证长上下文正确,这是逐行读源码才能看到的工程细节。
⚡ 逐行精讲三:Phi3Attention 多头注意力实现
融合 QKV 投影与多头切分
Phi-3 把 Q、K、V 三个线性层合并成一个qkv_proj,一次矩阵乘法后按列切片(modeling_phi3.py#L313-L323):
qkv = self.qkv_proj(hidden_states) query_states = qkv[..., :query_pos] # 前段 → Q key_states = qkv[..., query_pos : query_pos + kv_dim] # 中段 → K value_states = qkv[..., query_pos + kv_dim :] # 尾段 → V融合后对 GPU/NPU 更友好(减少 kernel 启动次数)。切完再view + transpose成"多头"形状:(batch, heads, seq_len, head_dim)。
缩放点积注意力 + KV Cache
注意力核心(modeling_phi3.py#L346-L365):
attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim) attn_weights = softmax(attn_weights, dim=-1, dtype=torch.float32) # 升精度 softmax attn_output = torch.matmul(attn_weights, value_states)- 除以 √head_dim:防止点积值过大导致 softmax 梯度消失,经典"缩放"。
- softmax 升 fp32:与 RMSNorm 同理,精度敏感操作先升精度。
- KV Cache:自回归生成时,上一步的 K、V 通过
past_key_value.update(...)缓存复用(modeling_phi3.py#L338-L340),避免重复计算历史 token——这也是 genai_config.json 里past_key_values输入输出的来源。 - GQA 支持:
repeat_kv(modeling_phi3.py#L239-L248)用于 KV 头少于 Q 头时的复制扩展;本项目num_key_value_heads = 32 = num_attention_heads,实际走的是标准 MHA,但代码保留了 GQA 能力。
三种注意力实现,一套权重
源码用字典注册了三种前向实现(modeling_phi3.py#L784-L788):
| 实现 | 类 | 特点 |
|---|---|---|
eager | Phi3Attention | 手工 matmul,可输出注意力权重,便于调试 |
flash_attention_2 | Phi3FlashAttention2 | IO 感知的快速实现,支持滑窗注意力 |
sdpa | Phi3SdpaAttention | PyTorch 原生scaled_dot_product_attention |
三者权重结构完全相同(继承自Phi3Attention),只是前向算法不同,加载模型时按attn_implementation参数选择。
🧩 组装:Phi3DecoderLayer 的完整数据流
一层解码器 = 两次 RMSNorm + 注意力 + MLP + 两条残差(modeling_phi3.py#L837-L856):
residual = hidden_states hidden_states = self.input_layernorm(hidden_states) # RMSNorm ① attn_outputs = self.self_attn(hidden_states, ...) hidden_states = residual + self.resid_attn_dropout(attn_outputs) # 残差连接 residual = hidden_states hidden_states = self.post_attention_layernorm(hidden_states) # RMSNorm ② hidden_states = self.mlp(hidden_states) # SwiGLU 前馈 hidden_states = residual + self.resid_mlp_dropout(hidden_states) # 残差连接这是标准的Pre-Norm + 残差结构:归一化放在子层之前,残差主干保持"原始信号"不被稀释,训练稳定。MLP 部分(Phi3MLP,modeling_phi3.py#L219-L235)用的是SwiGLU激活:gate_up_proj一次算出 gate 和 up 两路,up * silu(gate)相乘后经down_proj降回 3072 维。
32 层堆叠(nn.ModuleList,modeling_phi3.py#L1003-L1005)→ final RMSNorm →lm_head线性层把 3072 维映射到 32064 词表 logits(modeling_phi3.py#L1256-L1257)。
📊 config.json 关键参数速查表
| 参数 | 值 | 含义 |
|---|---|---|
hidden_size | 3072 | 隐层维度,RMSNorm 的 weight 长度 |
num_hidden_layers | 32 | 解码器层数 |
num_attention_heads/num_key_value_heads | 32 / 32 | 头数,每头 96 维 |
intermediate_size | 8192 | MLP 中间维度(SwiGLU) |
max_position_embeddings | 131072 | 最大支持上下文(128K) |
original_max_position_embeddings | 4096 | LongRoPE 长短系数切换点 |
rope_scaling.type | longrope | 位置编码扩展策略,系数校验见 configuration_phi3.py#L189-L227 |
rms_norm_eps | 1e-05 | RMSNorm 的数值稳定项 |
sliding_window | 262144 | 滑窗注意力窗口(大于 128K,实际不裁剪) |
quantization_config | quark / awq / uint4 / group 128 | AWQ 4-bit 量化配置(config.json#L27-L106) |
❓ 常见问题解答
Q1:为什么同时有 model.onnx、model.onnx.data 和 model.pb.bin?model.onnx是计算图(不含大权重),model.onnx.data是 ONNX 外部权重;model.pb.bin是 RyzenAI 执行引擎的外部权重文件,由 genai_config.json 的external_data_file指定。这种"图与数据分离"是 ONNX 部署的常规做法,方便按引擎各自加载。
Q2:上下文 128K,为什么 rai_config 里 prompt 只给 4096?rai_config.json 按 Ryzen AI 版本区分最大 prompt 长度:1.5.x 为 2048,1.6.0 及以上为 4096。结合hybrid_opt_max_seq_length: 4096与 LongRoPE 的 4096 切换点可以看出设计思路:prompt 控制在 4096 内走最优混合路径,超长输出由 LongRoPE 的 long_factor 平滑外推到 128K。
Q3:能直接微调这个包吗?本包是 UINT4 量化的部署产物,不建议直接微调。仓库提供了 sample_finetune.py(SFTTrainer + LoRA + DeepSpeed ZeRO-3 的完整示例),微调应基于原始模型权重,完成后再走量化导出流程。
写在最后
读懂 modeling_phi3.py 这条主线——RMSNorm 负责稳定数值、LongRoPE 负责记住位置、注意力负责融合信息、残差连接负责保送信号——基本就掌握了绝大多数现代 LLM 的骨架。Phi-3.5 的特别之处在于把 128K 长上下文、融合 QKV、三种注意力实现和 AWQ 量化都压缩进了同一个代码库里,是非常好的学习样本。
【免费下载链接】Phi-3.5-mini-instruct_rai_1.8.0_hybrid
相关推荐
从开发机到生产环境:Phi-3.5-mini-instruct_rai_1.8.0_hybrid部署分步实战指南
从开发机到生产环境:Phi 3.5 mini instruct_rai_1.8.0_hybrid部署分步实战指南 Phi 3.5 mini instruct_r
NYU-DLSP20 第12讲(三):注意力机制与 Transformer 从原理到 PyTorch 实现
NYU DLSP20 第12讲(三):注意力机制与 Transformer 从原理到 PyTorch 实现 本文是 NYU DLSP20(NYU Deep Le
示例工程DeepSeek-VL注意力机制:跨模态注意力实现原理
DeepSeek VL注意力机制:跨模态注意力实现原理 引言:多模态融合的技术挑战 在人工智能领域,视觉 语言模型(Vision Language Models
人工智能大模型多模态计算机视觉NLPDeepSeek
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考