Transformers TextIteratorStreamer 怎样流式读取生成结果
我第一次把本地模型接到聊天接口时,最明显的不适应不是模型调用本身,而是 model.generate() 默认要等整段结果准备好。界面只能在最后一次性拿到文本,用户感受到的就是“没有响应”。Transformers 提供的 TextIteratorStreamer 把这个问题拆成了两个角色:生成线程负责调用模型,消费端负责迭代已经准备好的文本片段。
官方地址:https://huggingface.co/docs/transformers/internal/generation_utils
最小可用结构是:创建TextIteratorStreamer,把它作为streamer传给model.generate,再让独立线程运行生成,主线程通过for new_text in streamer持续取得文本。
先理解 TextIteratorStreamer 的职责
它不是一个更快的模型,也不是网络传输协议,而是一个把可读文本放入队列、同时实现迭代接口的中间对象。生成侧调用 streamer 的写入方法,读取侧从 streamer 的迭代器中取出已经整理好的文本片段;两边不需要共享一个不断变化的字符串。
这个职责划分很重要:model.generate 仍然是耗时的生成动作,TextIteratorStreamer 只负责把生成结果交给下游。Hugging Face 官方文档给出的典型组合也是 tokenizer、model、streamer、Thread 和一个消费循环。

把 generate 放进后台线程
实际接入时,先准备好 tokenizer 和模型输入,再创建 streamer。关键点是把 streamer 放进同一组 generation_kwargs,随后在线程里调用 model.generate,当前消费侧才有机会同时读取文本。
from threading import Thread
from transformers import TextIteratorStreamer
# tokenizer、model 和 inputs 假设已经由上游准备好
streamer = TextIteratorStreamer(
tokenizer,
skip_prompt=True,
timeout=30.0,
)
# 将 streamer 放进 generate 的参数,生成线程会把文本片段交给它
generation_kwargs = dict(
inputs,
streamer=streamer,
max_new_tokens=128,
do_sample=False,
)
# 生成放到后台线程,当前线程专门消费可迭代文本
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
chunks = []
for new_text in streamer:
# 每次只处理当前片段,避免等待完整结果才刷新下游
chunks.append(new_text)
thread.join()
answer = "".join(chunks)
这里的 for 循环不是在轮询模型状态,而是在迭代 streamer 暴露出来的文本。生成线程结束后,streamer 会放入结束信号,消费循环自然结束。thread.join() 则让调用方在离开函数前确认后台线程已经收尾。
控制提示词与解码结果
如果把 skip_prompt 保持默认值,消费端可能同时拿到提示词和新生成的文本。聊天接口通常只希望返回新内容,因此可以创建 streamer 时显式设置 skip_prompt=True。这不是删掉模型输入,而是控制输出侧是否跳过输入提示部分。
解码相关选项可以通过额外关键字参数传入 streamer。不要把它和 generate 的采样参数混在一起:max_new_tokens、do_sample 属于生成调用;跳过提示词、跳过特殊 token 等则属于文本解码边界。
# 只把新生成的文本交给消费端,避免重复显示用户提示
streamer = TextIteratorStreamer(
tokenizer,
skip_prompt=True,
skip_special_tokens=True,
timeout=30.0,
)
generation_kwargs = {
**inputs,
"streamer": streamer,
# 这是生成上限,不是 streamer 的队列超时时间
"max_new_tokens": 128,
}
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
for piece in streamer:
# 下游可以把 piece 写入响应、拼接到缓冲区或交给 UI
print(piece, end="", flush=True)
thread.join()
中文或子词模型经常不是每生成一个 token 就立即形成一个完整词,因此消费端应把片段当作增量文本,不要假设每个片段都能独立解析成完整句子。
设置 timeout 并处理异常
默认情况下,streamer 在队列没有新文本时可能持续等待。如果生成线程因为模型、输入或设备错误提前退出,而消费端又没有超时边界,就很容易表现成“接口一直卡着”。构造 TextIteratorStreamer 时设置 timeout,可以让队列等待具备上限。
线程中的异常不会自动原样抛回当前消费循环,所以生产代码应当在线程目标函数里捕获异常,并把异常保存到一个线程安全可读的位置。下面的例子不把异常伪装成普通生成文本,而是在消费结束后明确抛出。
from threading import Thread
from transformers import TextIteratorStreamer
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, timeout=30.0)
worker_error = []
def run_generation():
try:
# 生成失败时由这里捕获,避免消费端只看到空流
model.generate(
**inputs,
streamer=streamer,
max_new_tokens=128,
)
except Exception as exc:
# 只记录异常对象,交给外层决定响应格式和日志策略
worker_error.append(exc)
thread = Thread(target=run_generation)
thread.start()
pieces = []
try:
for piece in streamer:
# 超时或结束信号应当被当作控制边界处理
pieces.append(piece)
finally:
# 无论消费是否正常结束,都等待生成线程离开
thread.join()
if worker_error:
# 让调用方看到真实生成错误,而不是误以为得到了空答案
raise worker_error[0]
answer = "".join(pieces)
这段代码的边界是“记录并上抛”异常。实际服务还可以把异常转换成结构化错误、取消响应或重试信号,但不要在消费端静默吞掉它。timeout 也不是生成总时长,它控制的是 streamer 等待文本片段的队列等待边界。

选择同步迭代还是异步扩展
当应用本身就是普通同步函数时,TextIteratorStreamer 加 Thread 足够直观:后台线程负责生成,当前线程直接用 for 循环拿结果。它的优势是改动小、职责清晰,也容易在最后调用 join 收尾。
如果消费端是原生异步应用,可以了解 AsyncTextIteratorStreamer。官方文档将它设计为异步迭代器,并提示应在协程内部初始化。不要为了“看起来更现代”而把同步线程方案和异步方案混写;先按调用栈选择一种边界。
import asyncio
from threading import Thread
from transformers import AsyncTextIteratorStreamer
async def read_stream():
# 异步 streamer 在协程内部创建,保持事件循环边界清晰
streamer = AsyncTextIteratorStreamer(tokenizer, skip_prompt=True)
kwargs = {**inputs, "streamer": streamer, "max_new_tokens": 128}
# 模型生成仍放在线程中,避免阻塞事件循环
thread = Thread(target=model.generate, kwargs=kwargs)
thread.start()
result = []
async for piece in streamer:
# 异步消费端可以把片段交给 SSE 或 WebSocket 层
result.append(piece)
thread.join()
return "".join(result)
# 由应用入口负责启动协程
# asyncio.run(read_stream())
同步和异步 streamer 的共同点是“生成端持续写、消费端持续读”;差别只在消费接口和事件循环边界。若只是要在同步请求里逐段返回文本,第一种写法通常更容易维护。
常见问题
TextIteratorStreamer 为什么要配合线程?
因为 model.generate 本身会占用当前调用路径。独立线程让消费端可以同时迭代 streamer;streamer 只是传递生成片段,不会自动把阻塞生成变成异步函数。
timeout 设置得越小越好吗?
不是。它是队列等待边界,过小会把正常的长推理间隔误判为超时。应结合模型首 token 延迟、设备负载和上层接口的取消策略设置。
为什么每次 piece 不是一个完整句子?
streamer 依据 tokenizer 的解码结果提供可读片段,token 边界和自然语言句子边界不同。下游应按增量文本拼接,若要按句展示,需要另加自己的缓冲和分隔判断。
生成线程报错时为什么消费端没有看到异常?
线程异常不会自动穿透到另一个线程。在线程目标函数中捕获并保存异常,消费结束后检查错误集合,才能让调用方获得真实失败原因。
小结
TextIteratorStreamer 的核心不是改变生成算法,而是把“生成”和“读取”拆开。准备好 tokenizer 与 inputs 后,把 streamer 传入 model.generate,用独立线程运行生成,再通过迭代器逐段消费;需要控制输出边界时使用 skip_prompt,需要故障边界时设置 timeout 并显式传递线程异常。同步应用从线程版开始,已经处于异步调用栈时再选择 AsyncTextIteratorStreamer。
Transformers 怎样按模型 chat template 组织消息
- 上一篇
- Transformers 怎样按模型 chat template 组织消息
- 下一篇
- Transformers 生成文本怎样理解 past_key_values 缓存
-
- 科技周边 · 人工智能 | 50分钟前 |
- RAG 文档切片怎样决定 overlap
- 278浏览 收藏
-
- 科技周边 · 人工智能 | 51分钟前 |
- ONNX 导出怎样声明动态 batch 和序列长度
- 131浏览 收藏
-
- 科技周边 · 人工智能 | 51分钟前 |
- RAG 检索结果怎样设置召回和重排的 Top K
- 491浏览 收藏
-
- 科技周边 · 人工智能 | 57分钟前 | LoRa PEFT merge_and_unload adapter unload disable_adapter delete_adapter
- PEFT 怎样卸载 adapter 并保留基础模型
- 485浏览 收藏
-
- 科技周边 · 人工智能 | 58分钟前 |
- Accelerate mixed_precision 怎样选择 fp16 或 bf16
- 386浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- Accelerate 怎样配置梯度累积
- 356浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 | skip take Hugging Face Datasets streaming IterableDataset shard
- IterableDataset 只有一个 shard 时怎么继续切分数据
- 441浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 | JSON split Hugging Face dataset Datasets
- Hugging Face Datasets 怎样从 JSON 加载数据集
- 266浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- Transformers 生成文本怎样理解 past_key_values 缓存
- 370浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- Transformers 怎样按模型 chat template 组织消息
- 118浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 474次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 533次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 552次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 506次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 324次使用
-
- CodeGeeX for Jetbrains IDEs正式上线!
- 2023-01-17 284浏览
-
- 技术阿里云实现ocr批量图片和pdf文件表格图片转换excel文档/支持票据图片提取/普通图片文字提取处理
- 2023-01-18 387浏览
-
- 直播预告|FeatureStore Meetup V2
- 2023-01-10 328浏览
-
- 深入浅出特征工程 – 基于 OpenMLDB 的实践指南(上)
- 2023-02-25 426浏览
-
- 开源机器学习数据库OpenMLDB v0.4.0产品介绍
- 2023-01-10 147浏览
