ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

NeMo SpeechLM2 数据集完全指南:从 Lhotse 格式、SALM/Duplex 数据管线到可恢复训练

NeMo SpeechLM2 数据集完全指南:从 Lhotse 格式、SALM/Duplex 数据管线到可恢复训练 NeMo SpeechLM2 数据集完全指南从 Lhotse 格式、SALM/Duplex 数据管线到可恢复训练【免费下载链接】SpeechA scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to-Speech)项目地址: https://gitcode.com/GitHub_Trending/nem/SpeechSpeechLM2 是 NeMo 中面向既能听懂语音、又能生成合适回复的语音大模型集合其训练数据同时包含音频与文本并围绕对话轮次conversation turns组织。本文以 docs/source/speechlm2/datasets.rst 为骨架结合 nemo/collections/speechlm2/data 目录下的真实实现与 examples/speechlm2/conf 配置示例系统讲解 DuplexS2S、SALM、DuplexSTT、DuplexEARTTS 四类数据集的设计、Lhotse manifest 的手工构建、SHAR 转换、DataModule 装配、断点续训与 AIStore GetBatch 加速读完即可动手准备自己的语音对话训练集并跑通训练闭环。概述speechlm2 集合的数据形态speechlm2 集合支持同时包含音频与文本的数据集用于训练能够理解语音并生成恰当回复的模型。官方文档将底层的数据加载职责委托给 Lhotse详见 docs/source/dataloaders.rst其是input_cfg结构、采样/分桶、indexed manifest 可恢复 dataloading 以及LhotseDataLoadingConfig字段模式的权威参考而本文聚焦 speech-LM 特有的内容datamodule 恢复契约、AIStore GetBatch、SALM/duplex 训练脚本中的会话类型语义。按模型能力划分speechlm2 提供了四类数据集数据集面向模型输入 → 输出DuplexS2SDataset通用双工语音到语音speech-to-speech模型对话音频 → 语音回复SALMDatasetSpeech-Augmented Language ModelSALM语音 文本 → 文本DuplexSTTDatasetDuplexSTTModel双工对话音频 → 文本回复DuplexEARTTSDatasetDuplex EARTTS 模型扩展 DuplexS2S附带 TTS 输出字段与音频提示它们都继承torch.utils.data.Dataset共同点是__getitem__接收一个Lhotse CutSet由采样器喂入一批 cut返回一批已对齐的 PyTorch 张量——数据采样由 Lhotse 采样器控制而非数据集本身这是理解整套管线的前提。DuplexS2S 数据集结构以对话轮次为中心的 Lhotse Cut一个典型的 speechlm2 数据集包含三要素音频文件源音频输入语音可能还有目标音频输出语音存储在 cut 的target_audio字段中文本转写输入与输出语音对应的文本角色标识区分说话人如uservsagent。数据组织围绕对话轮次conversation turns展开每个轮次携带来自 user 或 agent/assistant 的音频与文本。底层由 Lhotse 的 CutSet 格式管理一条 manifest 通常包含音频录音信息路径、时长、采样率监督信息转写文本、说话人角色、时间戳可选的自定义标注如target_audio、context、system_prompt。一个完整的 Lhotse cut 示例{ id: conversation_1, start: 0, duration: 10.7, channel: 0, supervisions: [ { id: conversation_1_turn_0, text: Can you help me with this problem?, start: 0, duration: 5.2, speaker: user }, { id: conversation_1_turn_1, text: I can help you with that., start: 5.2, duration: 3.1, speaker: assistant } ], recording: { id: conversation_1_user, path: /path/to/audio/conversation_1_user.wav, sampling_rate: 16000, num_samples: 171200, duration: 10.7 }, custom: { target_audio: { id: conversation_1_assistant, path: /path/to/audio/conversation_1_assistant.wav, sampling_rate: 22050, num_samples: 235935, duration: 10.7 } } }注意这里源音频user 声道放在recording字段而 agent 的回复音频放在custom.target_audio字段——这个设计使得合成数据生成时更换模型/说话人只需要替换target_audio非常灵活。数据集的关键处理流程DuplexS2SDataset在 nemo/collections/speechlm2/data/s2s_dataset.py 中实现其__getitem__会执行以下核心操作文本清洗cuts.transform_text(_strip_timestamps)用正则\|(\d)\|剥离文本中的时间戳 token如|0| Hey |3| how→Hey how见_strip_timestamps。Turn 识别每个 cut 的supervisions列表中是lhotse.SupervisionSegment对象代表带文本与说话人信息的对话轮次。说话人角色分离每条 supervision 的文本被 tokenizesupervision.speaker属于output_roles如agent/Assistant则视为模型输出属于input_roles如user/User则视为模型输入。Token 序列生成target_tokens与source_tokens数组的长度等于lhotse.utils.compute_num_frames(cut.duration, frame_length, cut.sampling_rate)frame_length典型 80ms决定 token 分配的时间分辨率。build_token_channel中每个 token 按对应音频片段的时间戳落位。Token 偏移计算每个轮次 token 的起始位置由compute_num_frames(supervision.start, frame_length, cut.sampling_rate)决定保证 token 与音频片段对齐。长度校验token 序列长于音频时长时发出警告并截断supervision.start越界时跳过该轮次记录shard_origin便于定位坏样本。源码中一个值得注意的细节EOS 不是紧跟文本末尾而是放在语音结束supervision.end对应的帧上文本末尾与 EOS 之间的空隙用 PAD 填充——这种segment 级对齐而非 word 级对齐的设计让模型学会把文本流与语音帧在时间轴上对齐。返回的字典包含source_audio/source_audio_lens、target_audio/target_audio_lens、target_tokens/target_token_lens、source_tokens/source_token_lens以及拼接后的target_texts。DuplexS2SDataset 实例化from nemo.collections.speechlm2.data import DuplexS2SDataset dataset DuplexS2SDataset( tokenizermodel.tokenizer, # 文本 tokenizer frame_length0.08, # 帧长秒决定 token 对齐的时间分辨率 source_sample_rate16000, # 输入音频采样率 target_sample_rate22050, # 输出音频采样率 input_roles[user, User], # 视为输入的角色 output_roles[agent, Assistant] # 视为输出的角色 )源码要求 tokenizer 必须支持 BOS 与 EOS否则直接 assert 失败PAD 缺失时退化为 0 并告警input_roles/output_roles缺省分别为[user]/[agent]。上述参数与 examples/speechlm2/conf/s2s_duplex.yaml 中data.frame_length: 0.08、data.source_sample_rate: 16000、data.target_sample_rate: 22050、input_roles: [user, User]、output_roles: [agent, Assistant]一一对应。SALM 数据集语音增强语言模型的多模态会话SALM 的数据既可以是普通语音转文本数据任何 NeMo 或 Lhotse 格式也可以是多轮对话数据集。单轮 ASR 数据的格式细节可参考 ASR 数据集文档 与多模态 dataloading 章节。用 lhotse_as_conversation 把 ASR 数据包装成两轮对话关键思想是SALM 训练用特殊的数据读取器lhotse_as_conversation把普通 Lhotse cut 构造成query response两轮多模态会话类型从而让单轮与多轮数据可以无缝混合训练。其底层实现位于 nemo/collections/common/data/lhotse/cutset.py 的read_lhotse_as_conversation先按常规read_cutset_from_config读入 cut支持 JSONL、tarred manifest、SHAR再把tags挂到每个样本上最后通过cut_to_conversation把每个 cut 转成NeMoMultimodalConversation。每个音频轮次在文本流中表现为一个 token由audio_locator_tag属性定义如|audioplaceholder|并在模型内部被自动加入 tokenizer训练/生成时该 token 会被替换为对应的音频段表示。NeMoMultimodalConversation类定义在 nemo/collections/common/data/lhotse/text_adapters.py 附近AudioTurn/TextTurn分别表示音频轮次与文本轮次。示例 1使用标准 ASR Lhotse manifestsJSONLdata: train_ds: prompt_format: llama3 # 根据模型选择 token_equivalent_duration: 0.08 input_cfg: - type: lhotse_as_conversation cuts_path: /path/to/librispeech_train_clean_100.jsonl.gz audio_locator_tag: |audioplaceholder| tags: context: Transcribe the following audio: # 可选系统提示词按需取消注释 # system_prompt: You are a helpful assistant that transcribes audio accurately.示例 2使用 tarred NeMo manifests- type: lhotse_as_conversation manifest_filepath: /path/to/tedlium_train_manifest.jsonl.gz tarred_audio_filepaths: /path/to/tedlium_shards/shard-{000000..000009}.tar audio_locator_tag: |audioplaceholder| tags: context: Write down what is said in this recording:示例 3使用 Lhotse SHAR 格式- type: lhotse_as_conversation shar_path: /path/to/fisher_shar/ audio_locator_tag: |audioplaceholder| tags: context: Listen to this clip and write a transcript:包装已有数据集组合 YAML也可以直接复用一份已有的数据集组合配置文件整体包进lhotse_as_conversationdata: train_ds: input_cfg: - type: lhotse_as_conversation input_cfg: /path/to/dataset_config.yaml audio_locator_tag: |audioplaceholder| tags: context: Transcribe the following audio: # Optional system prompt can be uncommented # system_prompt: You are a helpful assistant that transcribes audio accurately.自动构造的三轮会话lhotse_as_conversation会把每个 ASR 样例自动构造成两轮或三轮对话若提供了system_prompttag则对支持系统提示词的 LLM 添加一个 system 轮次一个 user 轮次包含音频与contexttag 指定的文本提示一个 assistant 轮次包含来自 cut supervision 文本的转写。提供contexttag 时它作为音频前的文本轮次被加入。SALMDataset 实例化from nemo.collections.speechlm2.data import SALMDataset dataset SALMDataset( tokenizermodel.tokenizer, # 文本 tokenizer )SALMDataset 源码 额外支持可选的multispeaker_cfgSerialized Output TrainingSOT 说话人活动目标提供时每个 batch 会附带由 RTTM 推导的spk_targets/spk_target_length用于重叠说话人场景。它返回audios/audio_lens、input_ids含audio_locator_tagtoken、loss_maskTrue 标记 assistant 回复中参与 loss 计算的 token。音频加载通过AudioSamples(fault_tolerantTrue, use_batch_loader..., mono_downmixTrue)完成且整个 batch 支持用FallbackDataset包装以在音频加载失败时回退到上一个 mini-batch见 nemo/collections/common/data/fallback 目录。AIStore GetBatchtarred 多模态会话的批量对象拉取实验性AIStore GetBatch 是服务端批量对象抓取 API。对于 tarred 多模态会话 manifestNeMoMultimodalConversationJsonlAdapter与NeMoMultimodalConversationShareGPTJsonlAdapter实现在 nemo/collections/common/data/lhotse/text_adapters.py设置环境变量USE_AIS_GET_BATCHtrue即可启用USE_AIS_GET_BATCHtrue python examples/speechlm2/salm_train.py \ --config-namesalm_automodel ...启用后会发生三件事适配器跳过打开 tar 文件转而构建 URL-backed cuts其AudioSource指向各 shard 的音频位置JSONL 的value字段被信任与 tar 布局一致SALMDataset以AudioSamples(use_batch_loaderTrue, fault_tolerantTrue, mono_downmixTrue)构造加载器每个 minibatch 只发一次批量抓取而非逐 cut 读取collate_conversation_audio_fault_tolerant把加载与 collation 委托给AudioSamples并丢弃在抓取中未存活的会话保留传统的 fault-tolerant 语义。不设置该环境变量时保持原来的逐 tar 迭代加载器。与 indexed: true 组合使用USE_AIS_GET_BATCHtrue可与LazyNeMoTarredIterator以及多模态会话适配器上的indexed: true共存indexed 模式负责 JSONL 驱动的 O(1) 全局索引与 graph-token 检查点AIStore GetBatch 负责实际音频抓取。此时音频 tar 的.idx伴生文件不再需要——迭代器完全跳过打开 tar直接发出 URL-backed cuts其AudioSource指向{tar_path}/{audio_filename}ais://...路径用typeurl否则用typefilemanifest JSONL 仍需要.idx伴生文件它们驱动 indexed 迭代器的 graph 与state_dict/load_state_dict往返音频字节在 collation 时由AudioSamples(use_batch_loaderTrue)惰性抓取每个 minibatch 发一次 GetBatch 请求。当 shard 存放在 AIStore 上、且希望同时获得 GetBatch 的网络效率与 indexed/stateful 管线的精确恢复保证时使用这种组合。相关索引工具是 scripts/dataloading/build_indexes.py。DuplexSTT 数据集双工语音转文本DuplexSTTDataset面向DuplexSTTModel处理双工对话音频并生成文本回复。与 DuplexS2S 不同它不要求 target 音频因此除了双工数据外还可以直接训练在标准 ASR、AST、SpeechQA 数据集上——这是它最重要的实用性。from nemo.collections.speechlm2.data import DuplexSTTDataset dataset DuplexSTTDataset( tokenizermodel.tokenizer, # 文本 tokenizer frame_length0.08, # 音频处理帧长 source_sample_rate16000, # 音频采样率 input_roles[User], # 作为输入的角色 output_roles[Assistant], # 要生成文本的角色 )源码实现 还包含几组值得了解的进阶能力aug_by_swap_role交换 User/Assistant 角色的数据增强需要cut.custom[target_audio]中存在 agent 音频会在_create_role_swapped_cut中重建 source 音频流early_interruption_prob随机截断带重叠的 agent 轮次模拟用户提前打断场景force_align_user_text用ForceAligner在 dataloader worker 中对用户文本做强制对齐与训练重叠执行predict_user_text/word_align_position/prepend_word_space控制用户文本是否带时间戳预测、词对齐位置left/right等add_mcq_val_prompt为验证集附加多选题提示词Answer the following multiple choice question with an explanation for the answer.还支持use_noise_aug/use_room_ir_aug/use_mic_ir_aug/use_codec_aug音频增强以及task asr时对空输入文本样本的过滤与空 batch 兜底。Duplex EARTTS 数据集带 TTS 输出的双工 S2SDuplexEARTTSDatasetnemo/collections/speechlm2/data/duplex_ear_tts_dataset.py扩展DuplexS2SDataset为 TTS 增加输出字段与音频提示speaker reference可选地把音频提示拼接到target_audio之前用于初始化 EARTTS 模型的说话人条件。它提供audio_prompt、audio_prompt_lens、non_prompt_mask、aligned_attention_mask、aligned_position_ids并通过context_audio字段支持自定义说话人参考音频同时保持与原数据格式完全兼容。构造函数特有参数add_audio_promptTrue是否在描述文本后追加音频/说话人提示audio_prompt_duration3.0提示音频时长秒p_drop_description随机丢弃文本描述的概率默认 0.0add_text_bos_and_eos_in_each_turnTrue每个说话轮次显式加 BOS/EOSnum_delay_speech_tokens0在语音 token 前插入的 PAD 数量人为延迟语音起始add_speech_delay对 target 音频左填充、source 音频右填充以维持对齐add_system_prompt/ignore_data_system_prompt系统提示词相关开关。在maybe_add_audio_prompt中提示音频按帧边界取整audio_prompt_duration * target_sample_rate向下对齐到target_samples_per_frame若参考音频不足则循环采样sample_audio_segments_repeat末尾补 2 帧静音source 声道对应位置补零填充。get_audio_prompt优先使用cut.context_audio否则从同角色轮次中随机选一段找不到 target 角色时退化为 5 秒静音并告警。aligned_attention_mask按段内 因果构造segment-id 相等且为下三角aligned_position_ids按对齐长度生成——这些正是对齐感知 Transformer 需要的输入。DataModule把数据集装进 PyTorch LightningDataModulenemo/collections/speechlm2/data/datamodule.py负责数据集的加载、准备与 batchingfrom nemo.collections.speechlm2.data import DataModule datamodule DataModule( cfg_data, # 数据配置字典data 命名空间 tokenizermodel.tokenizer, # 文本 tokenizer datasetdataset # DuplexS2SDataset / DuplexSTTDataset / SALMDataset 实例 )它完成为 dataloader 设置正确的数据并行DPrank——根据 trainer 的device_mesh计算dp_rank/world_size/DP 进程组兼容 ModelParallelStrategy 与 AutomodelParallelStrategy用 YAML 配置实例化 dataloaderget_lhotse_dataloader_from_config管理验证/测试的多个数据集validation_ds.datasets下每个条目是独立 dataloader可单独覆盖设置返回CombinedLoader(modemax_size)当use_stateful_dataloader: true时跨 checkpoint 持久化 train dataloader 的迭代器状态。可检查点/可恢复训练DataModule 在首次train_dataloader()调用时缓存 train dataloader并暴露委托给该 dataloader 的state_dict()/load_state_dict()Lightning trainer 会自动将它们写入每个 checkpoint。配置data: train_ds: indexed: true use_stateful_dataloader: true ...即可实现O(1) 恢复sampler RNG、bucketer 状态、multiplexer 选择 RNG、各 source 的迭代器游标、各 worker 的预取队列全部精确恢复无需重放。使用普通DataLoaderuse_stateful_dataloader未设置或为 False时state_dict/load_state_dict变为 no-op恢复退化为 Lhotse 的_fast_forward()重放路径。源码注释datamodule.py 第 94-116 行记录了历史上的一次坑曾在 DataModule 层做 gatherscatter但因 LightningFitLoop的_load_combined_loader_states在restore_datamodule之后执行、会用 rank-0 的旧状态覆盖 per-rank 状态导致非零 rank 的迭代器 shard 错乱约 14 分钟训练后报topology mismatch on resume。当前实现已改为由_PerRankStatefulDataLoader在 DP-only 进程组内 gather/scatter仅靠 wrapper 即可在恢复时保持各 rank 的 shard 划分同步。跨保存/恢复有两条硬约束num_workers与world_size必须一致StatefulDataLoader的硬性要求所有数据文件必须未压缩且带.idx伴生文件可用 scripts/dataloading/build_indexes.py 一次性构建见 dataloading 指南。分桶Bucketing提升训练效率分桶把时长相近的样本分组减少 padding、提升训练效率。关键参数batch_duration一个 batch 的目标累计时长秒bucket_duration_bins分桶的时长阈值列表use_bucketing开关num_buckets桶数量bucket_buffer_size参与桶分配的内存缓冲样本数。train_ds: # ... other settings batch_duration: 100 # 每个 batch 目标 100 秒 bucket_duration_bins: [8.94766, 10.1551, 11.64118, 19.30376, 42.85] # 时长阈值 use_bucketing: true # 启用分桶 num_buckets: 5 # 创建 5 个桶 bucket_buffer_size: 5000 # 桶分配缓冲大小启用后样本按时长分桶 → 同桶样本组成 batch → 实际 batch size 浮动以维持总时长稳定 → 目标batch_duration保证 GPU 显存的高效利用。收益包括减少 padding、增大有效 batch size、改善收敛与显存管理。数据配置YAML全貌数据路径与混合比例完全由 YAML 配置决定而非 Python 代码这让更换数据集组合无需改动代码data: train_ds: sample_rate: ${data.target_sample_rate} input_cfg: - type: lhotse_shar shar_path: /path/to/train_data seed: 42 shard_seed: randomized num_workers: 4 # 可选分桶设置 batch_duration: 100 bucket_duration_bins: [8.94766, 10.1551, 11.64118, 19.30376, 42.85] use_bucketing: true num_buckets: 5 bucket_buffer_size: 5000 # batch_size: 4 # 分桶的替代方案 validation_ds: datasets: val_set_name_0: shar_path: /path/to/validation_data_0 val_set_name_1: shar_path: /path/to/validation_data_1 sample_rate: ${data.target_sample_rate} batch_size: 4 seed: 42 shard_seed: randomized其中input_cfg支持lhotse_shar、lhotse_as_conversation等多种typelhotse_as_conversation的解析见上文 cutset.py 源码可并列多条实现数据混合。这套结构与 examples/speechlm2/conf/s2s_duplex.yaml 中的data段完全一致该文件把batch_size: null与分桶参数并用validation_ds.datasets.val_set_0.shar_path: ???等待用户填充。更完整的 Extended multi-dataset 配置格式见 ASR 文档。准备 S2S 数据集从音频到 manifest、再到 SHAR手工构建 Lhotse manifestfrom lhotse import CutSet, Recording, SupervisionSegment # 为 user 和 assistant 各创建一个 recording recording_user Recording( idconversation_1_user, path/path/to/audio/conversation_1_user.wav, sampling_rate16000, num_samples171200, duration10.7 ) recording_assistant Recording( idconversation_1_assistant, path/path/to/audio/conversation_1_assistant.wav, sampling_rate22050, num_samples235935, duration10.7 ) # 为该录音创建 supervision supervisions [ SupervisionSegment( idconversation_1_turn_0, recording_idconversation_1, start0, duration5.2, textCan you help me with this problem?, speakeruser ), SupervisionSegment( idconversation_1_turn_1, recording_idconversation_1, start5.5, duration3.1, textI can help you with that., speakerassistant ), ] # 创建 CutSet # assistant 的回复放在 target_audio 字段多模型/多说话人合成数据时可以方便地整体替换 cut recording_user.to_cut() cut.supervisions supervisions cut.target_audio recording_assistant cutset CutSet([cut]) # 保存到磁盘 cutset.to_file(path/to/manifest.jsonl.gz)转换为 SHAR 格式为了高效训练官方建议把 Lhotse manifest 转成 SHARSHarded ARchive格式from lhotse import CutSet from lhotse.shar import SharWriter cutset CutSet.from_file(path/to/manifest.jsonl.gz) cutset.to_shar(path/to/train_shar, fields{recording: flac, target_audio: flac}, shard_size100)fields指定哪些音频字段写入 shard 以及编码flac 压缩比高、解码快shard_size100控制每个 shard 的样本数。对于更大规模的数据也可参考 scripts/dataset_processing 与 scripts/speechlm2/export_conversations_to_tar.py 等现成工具链。用准备好的数据训练模型# 加载配置 config_path path/to/config.yaml cfg OmegaConf.load(config_path) # 训练数据路径在配置文件中 # cfg.data.train_ds.input_cfg[0].shar_path path/to/train_shar # 创建数据集与 datamodule dataset DuplexS2SDataset( tokenizermodel.tokenizer, frame_lengthcfg.data.frame_length, source_sample_ratecfg.data.source_sample_rate, target_sample_ratecfg.data.target_sample_rate, input_rolescfg.data.input_roles, output_rolescfg.data.output_roles, ) datamodule DataModule(cfg.data, tokenizermodel.tokenizer, datasetdataset) # 训练 trainer.fit(model, datamodule)实际运行通常直接使用官方脚本如python examples/speechlm2/s2s_duplex_train.py --config-names2s_duplex或salm_train.py、duplex_eartts_train.py等入口见 examples/speechlm2配合上述 YAML 配置即可。其中 examples/speechlm2/conf/s2s_duplex.yaml 展示了完整的端到端配置模型侧用pretrained_llm如 TinyLlama-1.1B-Chat、pretrained_audio_codec、pretrained_asr如 fastconformer hybrid streaming初始化音频编解码器冻结freeze_params: [^audio_codec\\..$]audio_loss_weight: 4、text_loss_weight: 3平衡两种 losstrainer 侧默认DDPStrategy、bf16-true、分桶式 train_ds 与多数据集 validation_ds。可用的 S2S 数据集来源目前尚没有公开的、专门按 Duplex S2S 格式组织的数据集但可以改造带有录音的对话数据集来使用Fisher CorpusSwitchboard CorpusCallHome使用 TTS 生成的合成对话数据集。使用时需要按上文方式把它们整理为带说话人角色标注的 Lhotse manifest。若目标是 SALM 或 DuplexSTT则单轮 ASR 数据LibriSpeech、TED-LIUM、Fisher 等scripts/dataset_processing 提供多种数据获取脚本经lhotse_as_conversation包装即可直接参与训练。小结SpeechLM2 的数据体系围绕对话轮次 角色分离 帧级对齐三个核心思想展开DuplexS2S/DuplexEARTTS 用target_audio承载输出语音并做帧级 token 对齐SALM 用audio_locator_tag把任意 ASR 数据包装成多模态会话DuplexSTT 免去 target 音频从而打通了标准 ASR/AST 数据。配合 DataModule 的分桶训练、indexed: trueuse_stateful_dataloader: true的 O(1) 断点续训以及可选 AIStore GetBatch 批量抓取即可在 examples/speechlm2/conf 提供的 11 份配置之上快速搭建自己的语音对话模型训练管线。【免费下载链接】SpeechA scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to-Speech)项目地址: https://gitcode.com/GitHub_Trending/nem/Speech创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表