学习进度 · 登录后可记录
模型项目地址:atomgit-ascend/GOT-OCR-2.0-hf 课程难度:中级 · 需要 FastAPI 基础和昇腾 NPU 开发经验 | 预估学时:30~45 分钟 部署架构:基于 transformers + torch_npu + FastAPI + Uvicorn API 服务部署
本章结束后,你能够:
AutoModelForImageTextToText 的昇腾适配模式本节课的目标是:在昇腾 NPU 上,部署 GOT-OCR-2.0-hf——阶跃星辰(StepFun)开源的通用 OCR 大模型,将其封装为 FastAPI HTTP 服务,支持上传图片并返回 OCR 识别文本。
GOT-OCR-2.0-hf 属于 ImageTextToText 类模型——输入一张图片,输出对应的文字描述或识别结果。与传统 LLM 的自回归文本生成不同,OCR 模型的核心挑战在于图像编码器(Vision Encoder)与文本解码器(Text Decoder)的协同计算,这对昇腾的算子融合和显存管理提出了独特的要求。
很多习惯了 CUDA 的开发者会认为:OCR 模型不就是多模态模型嘛,跟 VLM 一样部署就行——这个"一样"恰恰是最大的陷阱。
| 计算特征 | 纯文本 LLM | OCR 模型(GOT-OCR-2.0-hf) |
|---|---|---|
| 模型类型 | AutoModelForCausalLM | AutoModelForImageTextToText |
| 输入处理 | Tokenizer → Token IDs | Processor → 图像编码 + Token IDs 混合 |
| 核心计算 | Decoder Self-Attention | Vision Encoder(CNN/ViT)+ Decoder Cross-Attention |
| 输出解析 | 直接 decode token | 需要从 <|im_start|>assistant 标签中提取 OCR 文本 |
| 关键算子 | 纯 Cube(矩阵乘) | Cube(Attention)+ Vector(图像卷积/池化)混合型 |
┌──────────────────────────────────────────────────────┐
│ FastAPI + Uvicorn 服务层 │
│ ┌────────────────────────────────────────────────┐ │
│ │ main:app (port 8016) │ │
│ │ ├─ / → 健康检查 │ │
│ │ └─ /v1/orc/stepFun → OCR 推理接口(文件上传) │ │
│ └──────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────────────┐ │
│ │ stepFunService.py(核心推理链路) │ │
│ │ ├─ stepFunModel() → 单例模型加载 │ │
│ │ ├─ processor(image) → 图像编码 + 文本编码 │ │
│ │ ├─ model.generate() → 自回归 OCR 解码 │ │
│ │ └─ regex 提取 → OCR 文本清洗 │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
├─────────────────────┼────────────────────────────────┤
│ torch_npu + CANN 8.5 │
│ ┌──────────────┐ ┌─▼──────────────┐ │
│ │ GE 图编译 │ │ CUBE + Vector │ │
│ │ (算子融合) │ │ 混合执行 │ │
│ └──────────────┘ └────────────────┘ │
├──────────────────────────────────────────────────────┤
│ 昇腾 NPU 硬件层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Scalar │ │ Vector │ │ CUBE 矩阵乘 │ │
│ │ 控制流 │ │ 图像编码 │ │ 文本解码 │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘设计思路:GOT-OCR-2.0-hf 服务依赖 FastAPI、transformers、torch_npu 等。按照昇腾适配规范,所有第三方库必须使用一条 pip install 命令安装,源参数只在最后出现一次。绝对不能安装普通版本的 torch,必须安装 torch_npu。
# [依赖安装] 一条命令安装所有 Python 依赖
!pip install -U atomgit torch_npu transformers accelerate fastapi uvicorn python-multipart pillow -i https://mirrors.huaweicloud.com/repository/pypi/simple设计思路:GOT-OCR-2.0-hf 的 API 服务代码托管在 AtomGit 的 atomgit-ascend/GOT-OCR-2.0-hf 仓库中,包含 FastAPI 服务端代码、日志工具和测试图片。所有源码从 AtomGit 平台获取。
# [代码获取] 从 AtomGit 克隆 GOT-OCR-2.0-hf 服务代码
!mkdir -p /opt/atomgit
!git clone https://atomgit.com/atomgit-ascend/GOT-OCR-2.0-hf.git /opt/atomgit/GOT-OCR-2.0-hf设计思路:GOT-OCR-2.0-hf 模型权重托管在 AtomGit 平台,使用 atomgit_hub 的 snapshot_download 下载到本地固定路径。
# [模型下载] 从 AtomGit 平台下载 GOT-OCR-2.0-hf 模型
from atomgit_hub import snapshot_download
model_repo = "StepFun/GOT-OCR-2.0-hf"
local_dir = "/opt/atomgit/models/GOT-OCR-2.0-hf"
snapshot_download(model_repo, local_dir=local_dir)
print(f"模型已下载到: {local_dir}")设计思路:这是整个课程的核心知识点。原始 stepFunService.py 需要适配昇腾 NPU 环境,主要包含三个关键改造:
NPU_VISIBLE_DEVICES、ASCEND_RT_VISIBLE_DEVICES 等,确保 torch_npu 正确识别设备torch_npu.npu.is_available() 检测 NPU 可用性,可用则使用 npu:0,否则回退到 cpuAutoModelForImageTextToText(不是 CausalLM),配合 device_map 和 torch.bfloat16将以下完整代码写入 /opt/atomgit/GOT-OCR-2.0-hf/app/service/stepFunService.py,替换原文件全部内容:
# [核心适配] stepFunService.py 完整替换内容
# 写入路径:/opt/atomgit/GOT-OCR-2.0-hf/app/service/stepFunService.py
# ===== 强制 NPU 设置 - 必须在最开头 =====
import os
os.environ['NPU_VISIBLE_DEVICES'] = '0'
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
os.environ['ASCEND_DEVICE_ID'] = '0'
os.environ['DEVICE_ID'] = '0'
os.environ['TORCH_DEVICE_BACKEND_AUTOLOAD'] = '0'
# ========================================
import sys
import traceback
import time
import re
from utils.loggingConfig import get_logger
logger = get_logger('')
# ========== NPU 设备配置 ==========
import torch
import torch_npu
device = 'cpu'
devicemap = 'cpu'
try:
if torch_npu.npu.is_available():
device = 'npu:0'
devicemap = device
logger.info(f'✅ 检测到 NPU,使用设备: {device}')
torch.npu.set_device(0)
else:
logger.warning('⚠️ NPU 不可用,使用 CPU')
except Exception as e:
logger.error(f'NPU 检测异常: {e}')
device = 'cpu'
devicemap = 'cpu'
logger.info(f'最终设备配置 - device: {device}, devicemap: {devicemap}')
# ===============================
from transformers import AutoProcessor, AutoModelForImageTextToText
class stepFunModel():
_instance = None
_initialized = False
def __new__(cls):
if cls._instance is None:
cls._instance = super(stepFunModel, cls).__new__(cls)
return cls._instance
def __init__(self):
if stepFunModel._initialized:
return
model_path = os.getenv('STEPFUN_MODEL_PATH')
logger.info(f'模型路径: {model_path}')
logger.info(f'使用的 device_map: {devicemap}')
try:
model = AutoModelForImageTextToText.from_pretrained(
model_path,
device_map=devicemap,
torch_dtype=torch.bfloat16 if device != 'cpu' else torch.float32
)
processor = AutoProcessor.from_pretrained(model_path)
self._model = model
self._processor = processor
stepFunModel._initialized = True
actual_device = next(model.parameters()).device
logger.info(f'✅ 模型实际加载设备: {actual_device}')
logger.info(f'模型加载完成')
except Exception as e:
logger.error(f'模型加载失败: {e}')
traceback.print_exc()
raise
stepFunModel()
async def stepFunService(image_path):
logger.info(f"处理图片: {image_path}")
t1 = time.time()
model = stepFunModel()
inputs = model._processor(image_path, return_tensors="pt").to(device)
generate_ids = model._model.generate(
**inputs,
do_sample=False,
tokenizer=model._processor.tokenizer,
stop_strings="<|im_end|>",
max_new_tokens=4096,
)
full_output = model._processor.decode(generate_ids[0], skip_special_tokens=False)
# 从 assistant 标签中提取 OCR 文本
pattern = r'<\|im_start\|>assistant\n(.*?)<\|im_end\|>'
match = re.search(pattern, full_output, re.DOTALL)
if match:
ocr_result = match.group(1).strip()
else:
ocr_result = re.sub(r'<\|.*?\|>', '', full_output).strip()
t2 = time.time()
logger.info(f'OCR 耗时: {t2 - t1:.2f}秒')
return ocr_result环境变量必须在 import torch_npu 之前设置:NPU_VISIBLE_DEVICES、ASCEND_RT_VISIBLE_DEVICES 等变量影响 torch_npu 的初始化行为。如果在 import 之后才设置,设备发现逻辑可能已经完成,环境变量不生效。
单例模式(Singleton):stepFunModel 使用 __new__ + _initialized 实现单例,确保模型只加载一次。这在 FastAPI 多请求场景下非常重要——避免每次请求都重新加载模型。
OCR 输出解析:GOT-OCR-2.0-hf 的输出格式为 <|im_start|>assistant\n OCR文本 <|im_end|>,需要通过正则提取中间的文本。如果正则匹配失败,则回退到清除所有 <|...|> 标签的方式。
设计思路:Controller 层负责 HTTP 请求处理,接收上传的图片文件,调用 stepFunService 执行 OCR 推理,返回 JSON 格式的识别结果。需要验证文件格式、处理临时文件、异常捕获。
将以下完整代码写入 /opt/atomgit/GOT-OCR-2.0-hf/app/controller/modelExperienceController.py,替换原文件全部内容:
# [控制器适配] modelExperienceController.py 完整替换内容
# 写入路径:/opt/atomgit/GOT-OCR-2.0-hf/app/controller/modelExperienceController.py
import os
import time
from fastapi import APIRouter, UploadFile, File, Form
from typing import Optional
import shutil
from service.stepFunService import stepFunService
from utils.loggingConfig import get_logger
logger = get_logger('')
router = APIRouter()
ALLOWED_EXTENSIONS = os.getenv('ALLOWED_EXTENSIONS', '.tif,.png,.webp,.gif,.jpeg,.jpg,.bmp,.tiff').split(',')
@router.post("/v1/orc/stepFun")
async def stepFun(
file: UploadFile = File(...),
model: Optional[str] = Form(None)
):
logger.info(f"######### stepFun")
logger.info(f"########### file {file.filename}")
file_ext = os.path.splitext(file.filename)[1].lower()
if file_ext not in ALLOWED_EXTENSIONS:
return {
"success": False,
"code": 400,
"error_msg": f"不支持的文件格式: {file_ext}"
}
temp_dir = "/tmp/ocr_uploads"
os.makedirs(temp_dir, exist_ok=True)
temp_path = os.path.join(temp_dir, f"{int(time.time())}_{file.filename}")
try:
with open(temp_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
ocr_result = await stepFunService(temp_path)
return {
"success": True,
"code": 200,
"ocr_msg": ocr_result if ocr_result else ""
}
except Exception as e:
logger.error(f"OCR 处理失败: {e}")
return {
"success": False,
"code": 500,
"error_msg": str(e)
}
finally:
if os.path.exists(temp_path):
os.remove(temp_path)设计思路:通过 run.sh 脚本统一管理环境变量和服务启动。脚本中需要清除冲突变量(ASCEND_VISIBLE_DEVICES、CUDA_VISIBLE_DEVICES),设置 NPU 相关变量,并清理 Python 缓存避免旧代码残留。
创建 /opt/atomgit/GOT-OCR-2.0-hf/app/run.sh:
#!/bin/bash
echo "=========================================="
echo "启动 GOT-OCR-2.0-hf 服务 (uvicorn)"
echo "=========================================="
# 清除冲突变量
unset ASCEND_VISIBLE_DEVICES
unset CUDA_VISIBLE_DEVICES
# 设置环境变量
export NPU_VISIBLE_DEVICES=0
export ASCEND_RT_VISIBLE_DEVICES=0
export ASCEND_DEVICE_ID=0
export DEVICE_ID=0
export STEPFUN_MODEL_PATH=/opt/atomgit/models/GOT-OCR-2.0-hf
export TORCH_DEVICE_BACKEND_AUTOLOAD=0
echo "环境变量:"
echo " NPU_VISIBLE_DEVICES = $NPU_VISIBLE_DEVICES"
echo " STEPFUN_MODEL_PATH = $STEPFUN_MODEL_PATH"
# 清理 Python 缓存
find /opt/atomgit/GOT-OCR-2.0-hf -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null
find /opt/atomgit/GOT-OCR-2.0-hf -name "*.pyc" -delete
echo ""
echo "启动 uvicorn 服务..."
echo "服务地址: http://0.0.0.0:8016"
echo "OCR 接口: curl -X POST http://localhost:8016/v1/orc/stepFun -F 'file=@image.jpg'"
echo "=========================================="
cd /opt/atomgit/GOT-OCR-2.0-hf/app
python -m uvicorn main:app --host 0.0.0.0 --port 8016 --log-level info启动服务:
# [服务启动] 执行启动脚本
!chmod +x /opt/atomgit/GOT-OCR-2.0-hf/app/run.sh
!/opt/atomgit/GOT-OCR-2.0-hf/app/run.sh设计思路:服务启动后,在另一终端中通过 curl 上传测试图片,验证 OCR 推理是否正常工作。
# [OCR 推理] 使用仓库自带的测试图片验证
!curl -X POST http://localhost:8016/v1/orc/stepFun \
-F "file=@/opt/atomgit/GOT-OCR-2.0-hf/t-unit/image_ocr.jpg" \
-F "model=StepFun/GOT-OCR-2.0-hf"很多习惯了 CUDA 的开发者在这里容易踩坑:把 os.environ['NPU_VISIBLE_DEVICES'] = '0' 写在 import torch_npu 之后,结果发现模型还是加载到了 CPU 上。
根本原因:torch_npu 在 import 时会立即执行设备发现逻辑,读取 NPU_VISIBLE_DEVICES 和 ASCEND_RT_VISIBLE_DEVICES 环境变量来确定可见设备。如果在 import 之后才设置这些变量,设备发现已经完成,设置不生效。
解决方案:始终在模块最开头、import torch_npu 之前设置所有 NPU 相关环境变量:
# ✅ 正确:环境变量在 import 之前
import os
os.environ['NPU_VISIBLE_DEVICES'] = '0'
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
import torch_npu另一个高频踩坑点:系统或 shell 配置中可能预设了 CUDA_VISIBLE_DEVICES 或 ASCEND_VISIBLE_DEVICES,这些变量会干扰 torch_npu 的设备发现。
根本原因:CUDA_VISIBLE_DEVICES 和 ASCEND_VISIBLE_DEVICES 的优先级可能高于 NPU_VISIBLE_DEVICES。如果这些冲突变量存在,torch_npu 可能走 CUDA 路径或找不到设备。
解决方案:在 run.sh 中显式 unset 冲突变量:
unset ASCEND_VISIBLE_DEVICES
unset CUDA_VISIBLE_DEVICES
export NPU_VISIBLE_DEVICES=0
export ASCEND_RT_VISIBLE_DEVICES=0| 环境变量 | 默认值 | 说明 |
|---|---|---|
NPU_VISIBLE_DEVICES | 0 | NPU 可见设备 ID(必须在 import torch_npu 前设置) |
ASCEND_RT_VISIBLE_DEVICES | 0 | 昇腾运行时可见设备 |
ASCEND_DEVICE_ID | 0 | 昇腾设备 ID |
DEVICE_ID | 0 | 设备 ID(部分 CANN 版本需要) |
TORCH_DEVICE_BACKEND_AUTOLOAD | 0 | 禁用 PyTorch 设备后端自动加载 |
STEPFUN_MODEL_PATH | /opt/atomgit/models/GOT-OCR-2.0-hf | 模型本地目录 |
CUDA_VISIBLE_DEVICES | (必须 unset) | 冲突变量,必须清除 |
ASCEND_VISIBLE_DEVICES | (必须 unset) | 冲突变量,必须清除 |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 模型加载到 CPU 而非 NPU | 环境变量在 import 之后设置 | 确保所有 NPU 环境变量在 import torch_npu 之前设置 |
No CUDA GPUs available | CUDA_VISIBLE_DEVICES 未清除 | 在 run.sh 中 unset CUDA_VISIBLE_DEVICES |
OCR 输出包含 <|im_start|> 标签 | 正则提取失败 | 检查模型输出格式,回退到清除所有 <|...|> 标签 |
| 文件格式不支持 | 上传了非图片文件 | 支持格式:tif/png/webp/gif/jpeg/jpg/bmp/tiff |
服务启动报 ModuleNotFoundError | 依赖未安装或 Python 缓存残留 | 重新安装依赖,清理 __pycache__ 目录 |
| 端口 8016 被占用 | 旧服务未停止 | pkill -f uvicorn 杀掉旧进程 |
GET /响应:200 OK 表示服务正常运行
POST /v1/orc/stepFun
Content-Type: multipart/form-data请求参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | file | ✅ | 上传的图片文件 |
model | string | ❌ | 模型名称,如 StepFun/GOT-OCR-2.0-hf |
响应:
{
"success": true,
"code": 200,
"ocr_msg": "识别出的文字内容"
}本课程由 AtomGit AI 学习课程建设团队出品,严格遵循昇腾 NPU 开发规范。
登录后即可查看完整教程内容、运行代码和参
与学习互动
还没有账号?