1. 这不是API文档而是一份“用过三个月后才敢写的Hugging Face Datasets实战手记”你点开Hugging Face官网的datasets页面看到满屏的.map()、.filter()、.shuffle()、load_dataset(json, streamingTrue)……第一反应是不是这不就是Pandas换了个马甲等你真把一个20GB的JSONL日志文件load_dataset()进来发现内存直接飙到32GB、进程被OOM Killer干掉——才意识到这不是数据处理库而是一套专为大模型时代重新设计的数据流水线操作系统。我去年在做多模态对话数据清洗时用它处理了17TB原始文本图像描述对从最初被IterableDataset绕晕到后来能徒手写custom loading script对接内部对象存储踩过的坑足够填平三个GitHub Issues区。这篇不是教你怎么调用函数而是告诉你什么时候该用streamingTrue而不是默认加载为什么.map()的batchedTrue在GPU上反而更慢concatenate_datasets()背后藏着怎样的内存映射陷阱以及——最关键的——如何让load_dataset()不变成你训练Pipeline里最不可靠的单点故障。如果你正被数据加载卡住、被OOM折磨、被文档里“efficiently”这种词反复嘲讽那你需要的不是教程是这份带着血丝的实操笔记。2. 整体设计逻辑为什么Hugging Face Datasets不是Pandas而是一台可编程的数据引擎2.1 核心范式迁移从“内存表格”到“数据流图”传统数据处理比如Pandas本质是状态机数据一次性载入内存所有操作df.groupby().apply()都在这个静态快照上计算。而datasets的设计哲学是数据流图Dataflow Graph每个方法.map()、.filter()不立即执行而是向图中添加一个节点真正触发计算的是你显式调用.to_pandas()、.select()或开始迭代时。这带来三个根本性差异延迟执行Lazy Evaluation.map(lambda x: x[text].upper())不会立刻遍历全部数据只记录转换逻辑。当你后续调用.select(range(100))它只会对前100条执行upper()其余数据压根不加载。这对调试小样本极其友好——我常先用.select(range(10))验证逻辑再切回全量。零拷贝视图Zero-Copy Views.select([0,5,10])或.train_test_split()返回的不是新数据副本而是指向原数据的索引映射表。内存占用几乎为零切换训练/验证集毫无压力。对比Pandas的.iloc[]后者会复制数据块。分层抽象Layered Abstraction底层是Arrow二进制格式列式存储压缩率高中层是Dataset/IterableDataset接口上层是DatasetDict如{train: ds_train, test: ds_test}。这种分层让你能混合使用比如用IterableDataset流式读取原始日志.map()清洗后转成Dataset存为Arrow文件再用DatasetDict统一管理。我处理电商评论时就用这招把实时爬虫流和历史归档库无缝拼接。提示别被Dataset和IterableDataset的命名迷惑。Dataset适合能完整载入内存的中小数据集10GBIterableDataset才是处理超大数据的正确姿势——它不承诺随机访问但保证内存恒定。很多新手误以为“streamingTrue”只是省内存其实它彻底改变了数据访问模型你只能顺序迭代不能.select([1000])跳转。2.2 架构选型背后的硬约束为什么必须用ArrowHugging Face团队没选择Parquet或HDF5核心原因就两个Python生态兼容性和零拷贝跨语言能力。Arrow的C实现提供了Python、Rust、Java的原生绑定而datasets的底层加速器如numbaJIT编译直接操作Arrow内存。这意味着当你调用.map(..., batchedTrue, num_proc4)它不是用Python多进程multiprocessing而是调用Arrow的compute模块在C层并行处理列数据避免Python GIL锁死。实测在清洗文本时比纯Pythonconcurrent.futures快3.2倍。Arrow的DictionaryArray类型能将重复字符串如标签名positive/negative自动编码为整数ID内存占用直降70%。我处理医疗NER数据时实体类型列从1.2GB压缩到380MB且.filter()速度提升5倍——因为比较整数比比较字符串快得多。所有.save_to_disk()保存的都是Arrow文件.arrow后缀而非JSON/CSV。这是强制约定Arrow是唯一被保证能无损复现所有datasets特性的格式。试图用pandas.read_parquet()读取.arrow文件会报错——因为Arrow和Parquet虽同源但序列化协议不同。2.3 场景驱动的方案取舍什么情况下该放弃Datasets再强大的工具也有边界。根据我处理过23个NLP/CV项目的统计以下场景建议绕过datasets改用更底层方案实时流式推理Real-time Inference当你的服务需要毫秒级响应如聊天机器人IterableDataset的启动开销解析JSON Schema、建立Arrow Reader可能达200ms。此时直接用ijson流式解析JSONL配合torch.utils.data.IterableDataset自定义迭代器延迟压到15ms内。超细粒度内存控制若需精确控制每批数据的内存驻留如GPU显存敏感的多任务学习datasets的缓存机制.cache_files可能与你的策略冲突。这时用webdataset加载tar包手动管理mmap区域更可控。非结构化二进制数据datasets对图像/音频支持有限需转为base64或路径引用。处理10万张RAW格式天文图像时我直接用zarr数组存储datasets只存元数据索引避免base64膨胀33%。记住datasets是为大规模离线预处理模型训练优化的不是万能胶水。它的价值在于统一了从数据加载、清洗、切分到保存的整个链条让你少写50%的胶水代码——但前提是你的工作流符合这个范式。3. 核心功能深度拆解从原理到避坑的完整实操链3.1 Streaming模式不是开关而是一套全新的数据契约启用streamingTrue绝非加个参数那么简单。它强制你接受一套新的数据契约放弃随机访问拥抱顺序流。看一个真实案例我们有一份1.2TB的用户行为日志JSONL格式每行含user_id,timestamp,event_type,payload。目标是提取所有event_typepurchase的记录并按user_id分组统计购买次数。# ❌ 错误示范以为streaming只是省内存 ds load_dataset(json, data_fileslogs.jsonl, streamingTrue) # 此时ds是IterableDataset但下面这行会报错 filtered ds.filter(lambda x: x[event_type] purchase) # TypeError: IterableDataset has no filter method # ✅ 正确路径用迭代器组合 ds_iter iter(ds) # 获取迭代器 def purchase_filter(iterator): for sample in iterator: if sample[event_type] purchase: yield sample purchase_ds IterableDataset.from_generator(purchase_filter, gen_kwargs{iterator: ds_iter}) # 现在purchase_ds是新的IterableDataset只能迭代 for i, sample in enumerate(purchase_ds): if i 10: break # 只取前10条 print(sample[user_id])关键点解析IterableDataset没有.filter()、.map()等方法只有.from_generator()。你必须自己写生成器函数把逻辑封装进去。from_generator()的gen_kwargs参数用于传入外部状态如数据库连接、配置字典避免闭包污染。内存恒定原理purchase_filter每次yield一个样本后前一个样本的内存立即释放。无论日志多大内存峰值≈单条记录大小约2KB。注意streamingTrue下.map()依然可用但batchedTrue会被忽略无法批量因无全局长度。且.map()的num_proc参数失效——因为无法并行化一个未知长度的流。想加速得用concurrent.futures.ThreadPoolExecutor在生成器内部并行解析。3.2 Map操作为什么batchedTrue在GPU上反而更慢.map()是数据清洗的核心但它的性能陷阱最多。先看基础用法# 基础文本清洗去除HTML标签转小写 def clean_text(example): import re text re.sub(r[^], , example[text]) return {clean_text: text.lower()} ds_clean ds.map(clean_text) # 单样本模式问题来了单样本模式下re.sub()调用100万次Python函数调用开销巨大。于是文档推荐batchedTrue# 批量模式一次处理1000条 def clean_batch(batch): import re # batch[text] 是list[str]用列表推导式 cleaned [re.sub(r[^], , t).lower() for t in batch[text]] return {clean_text: cleaned} ds_clean ds.map(clean_batch, batchedTrue, batch_size1000)但当我把clean_batch迁移到GPU上用transformers的pipeline性能暴跌# ❌ GPU版批量清洗错误 from transformers import pipeline pipe pipeline(text-classification, device0) def gpu_clean_batch(batch): # 对batch[text]中每条文本单独调用pipeline——灾难 results [pipe(t)[0][label] for t in batch[text]] # 每次调用都触发GPU kernel launch return {label: results}为什么慢因为pipeline的batch推理需要输入是list[str]而上面代码是逐条调用。正确做法是让pipeline自己批处理# ✅ 正确GPU批量清洗 def gpu_clean_batch(batch): # 一次性传入整个batch让pipeline内部优化 results pipe(batch[text]) # 输入list输出list[dict] labels [r[label] for r in results] return {label: labels} ds_clean ds.map(gpu_clean_batch, batchedTrue, batch_size32) # GPU batch_size通常32/64更深层原理batchedTrue的本质是把Python层的循环下推到C层。Arrow的compute模块能对整列数据用SIMD指令并行处理。所以batchedTrue的最佳搭档是向量化函数如numpy、pandas操作而非Python循环。我的经验法则CPU清洗batchedTruenumpy向量化如np.char.lower()比单样本快8倍GPU清洗batchedTruepipeline原生批处理比单样本快15倍纯Python逻辑batchedFalse避免Python层批量带来的内存碎片。3.3 Concatenate合并数据集时的内存幻觉与真相concatenate_datasets([ds1, ds2, ds3])看似简单实则暗藏玄机。常见误区是认为它像Pandas的pd.concat()一样生成新数据集。真相是它创建一个虚拟的联合视图不复制数据。from datasets import concatenate_datasets # ds1: 500万条存于disk1/ # ds2: 300万条存于disk2/ # ds3: 200万条存于disk3/ ds_combined concatenate_datasets([ds1, ds2, ds3]) print(len(ds_combined)) # 输出10000000但内存占用≈ds1ds2ds3的索引大小10MB # 访问第600万条实际从ds2中读取因ds1占前500万 sample ds_combined[6000000] # 自动路由到ds2这带来两大优势零拷贝合并合并10个10GB数据集内存不增耗时1秒异构存储支持ds1可来自S3ds2来自本地SSDds3来自HDFSconcatenate_datasets自动适配各存储后端。但陷阱在于缓存机制。当你首次访问ds_combined[0]它会加载ds1的缓存访问ds_combined[5000000]加载ds2缓存。如果磁盘I/O慢首次访问不同数据集会有明显延迟。解决方案# 预热缓存在训练前主动触发各数据集的首条读取 for ds in [ds1, ds2, ds3]: _ ds[0] # 强制加载缓存 # 或更激进用多进程预热适合网络存储 from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers3) as executor: list(executor.map(lambda x: x[0], [ds1, ds2, ds3]))实操心得concatenate_datasets后永远用.to_dict()检查前几条是否正确路由。曾有个项目因ds2的schema少一个字段导致合并后所有ds2样本的该字段为None训练三天才发现——因为.map()时None被静默跳过。3.4 Metrics集成不只是计算指标更是数据质量的探针datasets的load_metric()常被当作“算个F1值”的工具但它真正的价值是作为数据质量监控探针。看一个典型场景我们用GPT-4生成了10万条问答对需过滤低质量样本如答案为空、问题过短。from datasets import load_metric f1_metric load_metric(f1) # 定义质量探针函数 def quality_probe(examples): probes { answer_length: [len(a) for a in examples[answer]], question_length: [len(q) for q in examples[question]], has_answer: [bool(a.strip()) for a in examples[answer]] } return probes # 在map中嵌入探针 ds_probed ds.map(quality_probe, batchedTrue, remove_columns[answer, question]) # 统计结果 print(Answer length mean:, np.mean(ds_probed[answer_length])) print(Empty answers ratio:, 1 - np.mean(ds_probed[has_answer]))但更强大的是自定义Metric类它能保存中间状态# 自定义探针检测答案是否包含问题关键词暗示抄袭 class KeywordLeakageMetric: def __init__(self): self.leakage_count 0 self.total_count 0 def add_batch(self, predictions, references): for pred, ref in zip(predictions, references): # 检查pred是否包含ref的关键词简化版 if any(word in pred.lower() for word in ref.split()[:3]): self.leakage_count 1 self.total_count 1 def compute(self): return {leakage_ratio: self.leakage_count / self.total_count if self.total_count else 0} leakage_metric KeywordLeakageMetric() leakage_metric.add_batch(ds[answer], ds[question]) print(leakage_metric.compute()) # {leakage_ratio: 0.12}这让我们能在数据加载阶段就发现生成数据的系统性缺陷如12%的答案直接复制问题而非等到训练后模型表现异常才排查。这才是Metrics的正确打开方式——它不是训练后的总结报告而是数据流水线中的质量门禁。3.5 Custom Loading Script当标准加载器不够用时的终极武器官方支持json,csv,parquet等格式但现实世界的数据源千奇百怪。比如我们接入的金融行情API返回的是带时间戳的二进制流每帧含{timestamp: int64, bid: float32, ask: float32}需解码为Arrow格式。这时必须写自定义加载脚本。脚本结构finance_loader.py# finance_loader.py from datasets import DatasetBuilder, BuilderConfig from datasets.utils.file_utils import get_datasets_user_agent import requests import struct import numpy as np class FinanceConfig(BuilderConfig): def __init__(self, urlNone, **kwargs): super().__init__(**kwargs) self.url url class FinanceDatasetBuilder(DatasetBuilder): BUILDER_CONFIG_CLASS FinanceConfig BUILDER_CONFIGS [FinanceConfig(namedefault, version1.0.0)] def _info(self): from datasets import Features, Value return DatasetInfo( featuresFeatures({ timestamp: Value(int64), bid: Value(float32), ask: Value(float32) }) ) def _split_generators(self, dl_manager): # dl_manager负责下载/缓存这里直接流式读取 return [ SplitGenerator( nameSplit.TRAIN, gen_kwargs{url: self.config.url} ) ] def _generate_examples(self, url): # 流式解析二进制 response requests.get(url, streamTrue, headers{User-Agent: get_datasets_user_agent()}) buffer b i 0 while True: chunk response.raw.read(1024) if not chunk: break buffer chunk # 每20字节一帧8字节timestamp 4字节bid 4字节ask while len(buffer) 20: frame buffer[:20] buffer buffer[20:] ts, bid, ask struct.unpack(Qff, frame) # 小端序 yield i, {timestamp: ts, bid: bid, ask: ask} i 1使用方式# 加载自定义数据集 ds load_dataset(./finance_loader.py, urlhttps://api.finance.com/stream) # 自动应用_builder_config返回标准Dataset关键要点_split_generators()返回SplitGenerator定义数据分割逻辑_generate_examples()是核心必须yield (id, example)支持流式/分页/增量加载dl_manager提供下载缓存但流式场景可绕过直接用requestsget_datasets_user_agent()确保请求头合规避免被API限流。我用这套机制接入了7个内部数据源从Kafka Topic到MongoDB聚合管道全部无缝集成到datasets生态。它证明了一点datasets不是封闭系统而是开放的数据协议——只要你遵循_generate_examples()的契约任何数据都能成为它的公民。4. 实操全流程从原始日志到可训练数据集的端到端拆解4.1 场景设定构建一个千万级电商评论情感分析数据集需求明确整合3个来源的评论数据——source_a.jsonl1200万条格式{review_id: a1, text: 很好用, rating: 5}source_b.parquet800万条格式{review_id: b1, content: 太差了退货, score: 1}source_c.csv500万条格式review_id,text,sentimentsentiment为positive/negative目标产出统一格式的Dataset含字段{id: str, text: str, label: int}0negative, 1positive并按8:1:1切分训练/验证/测试集。4.2 分步实现每一步都附带血泪教训步骤1加载各数据源统一Schemafrom datasets import load_dataset, concatenate_datasets, DatasetDict # 加载source_aJSONL ds_a load_dataset(json, data_filessource_a.jsonl, splittrain) # 重命名字段映射rating到label ds_a ds_a.rename_column(review_id, id).rename_column(text, text) ds_a ds_a.map(lambda x: {label: 1 if x[rating] 4 else 0}) # 加载source_bParquet注意Parquet需指定列类型 ds_b load_dataset(parquet, data_filessource_b.parquet, splittrain) # 处理字段名和label映射 ds_b ds_b.rename_column(review_id, id).rename_column(content, text) ds_b ds_b.map(lambda x: {label: 0 if x[score] 2 else 1}) # 加载source_cCSV需处理缺失值 ds_c load_dataset(csv, data_filessource_c.csv, splittrain) # CSV可能有空行先过滤 ds_c ds_c.filter(lambda x: x[text] and x[text].strip()) ds_c ds_c.map(lambda x: {label: 1 if x[sentiment]positive else 0}) # 合并三者 ds_all concatenate_datasets([ds_a, ds_b, ds_c]) print(f合并后总条数: {len(ds_all)}) # 应为2500万踩坑记录source_c.csv中有BOM头\ufeff导致review_id字段名变成\ufeffreview_idrename_column失败。解决方案用pandas.read_csv(..., encodingutf-8-sig)先预览或在load_dataset中加encodingutf-8-sig参数。步骤2数据清洗与标准化import re import unicodedata def normalize_text(example): text example[text] # 移除多余空白 text re.sub(r\s, , text.strip()) # 标准化Unicode如全角标点转半角 text unicodedata.normalize(NFKC, text) # 移除控制字符 text re.sub(r[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f], , text) return {text: text} # 关键batchedTrue 向量化否则2500万条要跑3小时 ds_clean ds_all.map( normalize_text, batchedTrue, batch_size10000, # 大batch减少Python调用 num_proc8 # 用8核CPU并行 )实测对比batchedFalse耗时2h17minbatchedTruebatch_size10000耗时11min。num_proc8在32核机器上收益饱和再多进程反而因IPC开销变慢。步骤3过滤低质量样本def quality_filter(example): # 过滤过短/过长文本 if len(example[text]) 5 or len(example[text]) 500: return False # 过滤纯数字/符号 if re.fullmatch(r[0-9\s\W], example[text]): return False return True ds_filtered ds_clean.filter(quality_filter) print(f过滤后剩余: {len(ds_filtered)}) # 通常剩2200万左右步骤4切分数据集并保存# 切分8:1:1 ds_split ds_filtered.train_test_split(test_size0.2, seed42) ds_train ds_split[train] ds_temp ds_split[test].train_test_split(test_size0.5, seed42) ds_valid, ds_test ds_temp[train], ds_temp[test] # 构建DatasetDict final_ds DatasetDict({ train: ds_train, validation: ds_valid, test: ds_test }) # 保存为Arrow格式推荐 final_ds.save_to_disk(./ecommerce_sentiment_v1) # 验证保存成功 loaded_ds load_from_disk(./ecommerce_sentiment_v1) print(loaded_ds[train][0]) # 查看第一条注意save_to_disk()会创建子目录train/,validation/,test/每个目录下是.arrow文件。不要手动修改这些文件——datasets的缓存机制依赖其完整性。4.3 性能调优让整个流程提速3倍的关键参数在2500万条数据集上上述流程默认耗时约45分钟。通过以下调优压缩至14分钟参数默认值优化值效果原理batch_size(map)10001000035%速度减少Python函数调用次数提升Arrow向量化效率num_proc(map/filter)1860%速度充分利用CPU多核但超过物理核数收益递减keep_in_memory(load)FalseTrue (仅小数据)20%速度避免磁盘I/O但需内存充足2500万条约8GBcache_dir~/.cache/huggingface/fast_ssd/cache50%速度SSD比HDD随机读写快10倍最终优化版代码# 加载时指定高速缓存目录 ds_a load_dataset( json, data_filessource_a.jsonl, splittrain, cache_dir/fast_ssd/cache ) # map时启用大batch和多进程 ds_clean ds_all.map( normalize_text, batchedTrue, batch_size10000, num_proc8, load_from_cache_fileTrue # 启用缓存避免重复计算 )5. 常见问题与排查技巧实录那些文档不会告诉你的细节5.1 内存爆炸的5种原因及对应解法当你的load_dataset()触发OOM别急着升级服务器先对照这张表排查现象根本原因解决方案验证命令启动即OOM数据集过大load_dataset()尝试加载全部索引改用streamingTrue或分片加载data_files{train: [part1.jsonl, part2.jsonl]}ps aux --sort-%mem | head -5.map()时OOMbatchedFalse 复杂Python逻辑每条样本创建大量临时对象改用batchedTrue 向量化或用numba.jit编译函数import tracemalloc; tracemalloc.start().save_to_disk()时OOMArrow写入时需内存缓冲区默认128MB不足设置环境变量export PYARROW_DEFAULT_MEMORY_POOLsystemecho $PYARROW_DEFAULT_MEMORY_POOL多进程OOMnum_proc8但每个进程都加载完整数据集副本改用IterableDataset流式处理或用dask分布式htop观察进程数与内存分布缓存文件OOM.cache_files累积过多未清理定期运行datasets.cleanup_cache_files(/path/to/dataset)du -sh ~/.cache/huggingface/datasets/*实操技巧用tracemalloc精准定位内存热点import tracemalloc tracemalloc.start() ds_clean ds.map(your_func) # 触发OOM前中断 snapshot tracemalloc.take_snapshot() top_stats snapshot.statistics(lineno) for stat in top_stats[:10]: print(stat) # 显示消耗内存最多的10行代码5.2 字段丢失/类型错误的隐形杀手最让人抓狂的问题.map()后某些字段突然消失或label从int64变成float64。根源在于Arrow的类型推断机制。字段消失当map函数返回的字典中某个key对应的value为NoneArrow会将其类型设为null并在后续操作中静默丢弃。例如def buggy_map(example): if example[text]: # 文本为空时返回None return {clean_text: example[text].strip()} # 忘记else分支返回None → clean_text列被删类型漂移Arrow对混合类型敏感。若batch[label]中既有int又有float会统一转为float64。解决方案def safe_map(batch): labels [] for l in batch[label]: # 强制转int避免float混入 labels.append(int(l) if l is not None else 0) return {label: labels}终极防御在关键步骤后用.features检查Schemaprint(ds_clean.features) # 输出Features({text: Value(dtypestring), label: Value(dtypeint64)}) if ds_clean.features[label].dtype ! int64: raise ValueError(Label type changed!)5.3 网络数据源的稳定性保障从S3/HTTP加载数据时网络抖动会导致load_dataset()失败。内置重试机制有限需手动加固from datasets import load_dataset import time import random def robust_load_dataset(*args, **kwargs): max_retries 5 for attempt in range(max_retries): try: return load_dataset(*args, **kwargs) except Exception as e: if attempt max_retries - 1: raise e wait (2 ** attempt) random.uniform(0, 1) # 指数退避 print(fAttempt {attempt1} failed: {e}. Retrying in {wait:.2f}s...) time.sleep(wait) return None # 使用加固版 ds robust_load_dataset(json, data_filess3://bucket/data.jsonl)5.4 自定义Metric调试为什么compute()总是返回0自定义Metric类常因状态未正确更新而失效。核心原则所有状态必须在add_batch()中更新compute()只负责汇总。错误示范class BadMetric: def __init__(self): self.count 0 # 实例变量但未在add_batch中更新 def add_batch(self, preds, refs): # 忘记self.count len(preds) pass def compute(self): return {count: self.count} # 永远是0正确写法class GoodMetric: def __init__(self): self._total 0 # 用_前缀表明私有 def add_batch(self, preds, refs): self._total len(preds) # 显式累加 def compute(self): return {total: self._total}调试技巧在add_batch()中打印len(preds)确认是否被调用用dir(metric_instance)检查实例属性是否被创建。6. 进阶技巧让Datasets成为你数据工程的中枢神经6.1 与Dataloaders无缝桥接PyTorch/TensorFlow的最优实践datasets不是独立王国它必须融入训练框架。最佳实践是用datasets做预处理用框架原生Dataloader做批处理。PyTorch示例from torch.utils.data import DataLoader from datasets import load_dataset ds load_dataset(imdb, splittrain) # 预处理tokenize但不padding留给Dataloader做 def tokenize_function(examples): return tokenizer(examples[text], truncationTrue, max_length512) ds_tokenized ds.map(tokenize_function, batchedTrue, remove_columns[text, label]) # 创建PyTorch Dataset class HFDataset(torch.utils.data.Dataset): def __init__(self, hf_dataset): self.hf_dataset hf_dataset def __len__(self): return len(self.hf_dataset) def __getitem__(self, idx): item self.hf_dataset[idx] # 转为tensor但不stackDataloader会做 return { input_ids: torch.tensor(item[input_ids]), attention_mask: torch.tensor(item[attention_mask]), labels: torch.tensor(item[label]) } # Dataloader负责动态padding def collate_fn(batch): input_ids torch.nn.utils.rnn.pad_sequence( [b[input_ids] for b in batch], batch_firstTrue, padding_valuetokenizer.pad_token_id ) attention_mask torch.nn.utils.rnn.pad_sequence( [b[attention_mask] for b in batch], batch_firstTrue, padding_value