
写这篇文章之前我刚在一台8卡海光DCU服务器上把Kubernetes集群里的vDCU配额重新调了一遍顺手把CubeStudio平台上的DeepSeek推理服务从整卡模式切成vDCU共享模式整个过程踩了不少坑但最后跑通了。这些年Kubernetes和AI平台的组合越来越常见但真正要把国产加速卡——尤其是海光DCU这种基于GPGPU架构的算力——接进K8s并且做到整卡调度、共享切分、虚拟化隔离全支持网上能直接抄的作业其实不多。这篇文章我就把这次CubeStudio适配海光DCU的实操过程完整拆开讲从K8s设备插件原理讲到两种vDCU虚拟化的区别再落到DeepSeek实际部署适合正在做国产算力平台纳管、或者想把DCU集群资源利用率提上来的同学参考。1. 内容整体设计与思路拆解1.1 为什么国产DCU接入Kubernetes会成为一道坎先说一个背景现在大部分AI平台都跑在Kubernetes上这基本是行业共识了。Kubernetes本身对CPU、内存这类基础资源的管理非常成熟但对GPU、DCU这类异构加速卡的支持核心机制其实是靠Extended Resource加Device Plugin这套东西来扩展的。NVIDIA有官方提供的device pluginAMD有对应的k8s-device-plugin但海光DCU生态的成熟度相比前两者还是有差距的早期版本基本靠自研脚本和手动调度来凑合。这里有个点必须讲清楚DCU和GPU在架构上虽然都属于加速卡但海光DCU的软件栈是基于ROCm生态的跟CUDA体系完全不兼容。也就是说你K8s集群里的调度器根本不认识这张卡如果你不写设备插件去上报资源Kubernetes只把它当成一个普通的PCIe设备Pod调度的时候完全不知道哪台节点有空闲算力。所以接入这件事第一步不是想着怎么共享、怎么虚拟化而是先解决“让K8s认识DCU”的问题。CubeStudio在这里扮演的角色相当于在Kubernetes和业务之间加了一个AI平台层。它帮你把Notebook、训练任务、推理服务这些工作负载统一管理起来底层调度还是走K8s但对用户来说不用直接面对YAML和命令行。所以这次适配的完整链路是DCU驱动到ROCm软件栈再到K8s设备插件上报资源然后CubeStudio纳管资源池最后把DeepSeek这类模型任务跑起来。1.2 三种资源供给模式的选型考量我在这次适配里重点测了三种供给模式整卡调度、基于时间片切分的vDCU共享、基于显存和算力隔离的vDCU虚拟化。为什么要把这三种分开说因为它们的底层机制、隔离强度、适用场景完全不同选错了后面性能和安全都会出问题。整卡调度最简单粗暴Pod声明一张卡就是一张卡资源隔离天然存在谁也不影响谁。但这种模式的代价是利用率低尤其是跑一些小模型推理任务一张卡计算量根本跑不满显存和算力都在闲置。时间片模式的思路是让多个任务轮流用同一张卡类似CPU的时分复用好处是能提升整体吞吐但隔离性较差一个任务把算力吃满会影响同卡的其他任务。而显存隔离模式更像NVIDIA的MIG方案把一张卡按显存和算力切成多个独立小分区每个分区有独立的显存边界和算力配额隔离性比时间片强不少但切分粒度有限制不是想要多大就能切多大。这三种模式可以同时存在于一个集群里。实际操作中我建议按业务属性来划分核心大模型训练任务用整卡多个小模型推理服务用显存隔离vDCU开发调试和低优先级批处理任务扔到时间片vDCU池子里。后面我会把每种模式的具体配置方式都写出来。2. 核心细节解析与实操要点2.1 整卡模式原理、配置与适用场景先讲整卡因为这是最基础的模式也是后面理解vDCU的前提。Kubernetes识别异构资源的标准做法是设备插件启动后调用kubelet的Device Plugin API把节点上的DCU数量作为Extended Resource上报资源名一般可以自定义成类似hygon.com/dcu这样的格式。之后用户在Pod的resources里声明hygon.com/dcu: 1调度器就知道这个Pod要占一张卡然后通过预选和优选算法把Pod调度到有足够空闲DCU的节点上。配置整卡模式没什么玄学的核心就是保证设备插件和驱动版本匹配。我在实际操作中用的设备插件是海光官方提供的版本它启动后会自动检测节点上的DCU设备把数量上报给kubelet。这里要注意一个坑设备插件必须和kubelet的socket目录一致默认是/var/lib/kubelet/device-plugins/如果你改了kubelet的启动参数设备插件这边没改就会上报失败kubectl describe node看不到任何DCU资源。整卡模式还有一个细节容易忽略nvidia.com/gpu这类资源名是NVIDIA插件注册的你不能借用来上报海光DCU。自定义资源名的好处是灵活但也意味着你的业务Pod必须显式声明这个自定义资源名。如果Pod里只是写了标准的resources.limits而没有写hygon.com/dcu调度器根本不会分配DCU给它任务跑起来会直接报找不到设备。2.2 时间片vDCU算力共享的实现机制时间片vDCU的本质是用软件方式把一张卡的计算能力按时间分片。它的实现路径通常是这样的设备插件上报的资源不再是整数卡而是一个虚拟资源池比如一张物理卡被定义成10个vDCU单元Pod声明1个vDCU单元实际上拿到的是一张物理卡10%的时间片配额。底层通过修改ROCm的运行时调度策略让多个进程在同一张DCU上交替执行。这种模式最大的价值是把物理卡的利用率拉起来。我实测过如果一张卡上跑4个推理任务每个任务的batch size都不大整卡模式下一张卡只能跑一个而时间片模式可以同时跑多个整体吞吐能提升2到3倍。但代价也很明显算力隔离是软的如果其中一个任务把计算单元占满了同卡其他任务延迟会明显上升。所以这种模式不适合对延迟敏感的在线服务更适合离线批量推理、数据预处理这类后半夜跑的负载。配置时间片vDCU时有几个参数需要重点确认。一个是每个vDCU的最小时间片粒度这个值设置得太小会增加上下文切换开销太大又可能导致任务排队时间过长。我个人经验是取10ms到20ms之间比较合理。另一个是单卡vDCU数量上限这个决定了资源池的弹性上限但不要盲目调高因为时间片轮转的任务越多单任务的实际算力越少调度器不会因为你分配了1个vDCU单元就保证你能拿到对应的性能。2.3 显存隔离vDCU类似MIG的硬隔离方案如果说时间片vDCU解决的是“让更多人用上这张卡”那显存隔离vDCU解决的就是“让不同人用同一张卡还不互相干扰”。这种方案在实现上参考了NVIDIA MIG的思路是把物理卡的计算单元和显存划分成多个独立分区每个分区在硬件层面有显存边界算力配额也是预先分配好的。海光DCU的这套vDCU虚拟化我理解是基于ROCm的MIOpen、hip runtime层做了一层抽象让每个分区看起来像一张独立的逻辑卡。我在测试中发现显存隔离vDCU的切分方式通常是按比例配置的比如一张64GB显存的卡你可以切成2个32GB的vDCU或者1个48GB加1个16GB的组合。但切分粒度不是无限制的有最小显存规格限制也有最小算力配额限制。如果你想切4个8GB的小分区而每个分区的算力配额过低驱动会直接拒绝创建。所以配置前要先查清楚当前DCU型号支持的切分规格表避免在K8s里声明了一堆虚拟资源结果设备插件创建vDCU的时候报错。从使用体验来说显存隔离vDCU最接近整卡的感觉Deployment的YAML里除了资源名改成hygon.com/vdcu之外Pod内部几乎不需要做额外的适配。每个任务拿到的是一块独立显存显存不够不会影响别人别人爆显存也不会拖垮你。这种模式的短板也很直接单卡能切的vDCU数量有限而且切分之后单个分区得到的绝对算力是下降的。所以显存隔离vDCU比较适合生产环境的在线推理服务比如把一个大模型推理服务和一个小模型OCR服务放在同一张卡上互不干扰同时把卡用满。2.4 三种模式对比怎么给业务选合适形态对比维度整卡模式时间片vDCU显存隔离vDCU资源粒度物理卡整数时间片配额显存算力分区隔离强度硬件隔离软隔离性能受影响近硬件隔离单卡并发度1个任务6到10个任务2到8个分区适用场景大模型训练、高性能推理离线批量推理、开发调试在线推理、多模型共存配置复杂度低中高性能衰减无高并发时明显相对可控选型的时候别只看一张表还得结合业务实际。我的建议是如果你只有一个业务方任务以训练为主整卡是最省心的如果有多个业务方共用集群希望提高整体利用率那必须引入vDCU。至于是时间片还是显存隔离核心指标就是“对延迟是否敏感”。延迟敏感就选显存隔离能容忍排队就把时间片和显存隔离混合部署。3. 实操过程与核心环节实现3.1 环境准备驱动、ROCm与Kubernetes版本匹配开始配置之前先确认一下环境基线。我这次的测试集群是三台节点每台两张海光DCU卡操作系统是x86架构的Linux发行版Kubernetes版本是1.28CubeStudio版本相对较新容器运行时的GPU/Rocm支持已经内置了一部分但驱动层的准备仍然必须手动做。第一步是装驱动和ROCm栈。海光DCU的驱动不像NVIDIA那样一个run文件全搞定它会有单独的kernel module和userspace runtime包。我建议先确认内核版本是否在驱动支持的列表里否则编译kernel module时会报一堆头文件缺失的错误。安装之后用rocm-smi或者hy-smi查看设备状态确认DCU能被系统识别这一步能过后面流程才走得下去。第二步是确保容器运行时支持ROCm设备挂载。Kubernetes默认的containerd或者Docker运行时本身不会自动挂载DCU设备需要靠设备插件在分配资源的时候把设备节点和依赖库注入到容器里。设备插件本质上干的事情就是收到kubelet的分配请求后把/dev/hy*这类设备文件以及ROCm runtime需要的库路径通过CSI或者hostPath挂载方式暴露给Pod。所以你在配置设备插件时要留意它的权限和挂载源设置默认往往只挂/dev/dri这对NVIDIA是够的但对DCU还少了点东西。3.2 部署海光DCU设备插件让Kubernetes认识DCU设备插件的部署方式通常是一个DaemonSet在每个节点上跑一个Pod来上报资源。以下是我实际使用的部署文件要点你可以按这个结构去调整apiVersion: apps/v1 kind: DaemonSet metadata: name: hygon-dcu-device-plugin namespace: kube-system spec: selector: matchLabels: name: hygon-dcu-device-plugin template: metadata: labels: name: hygon-dcu-device-plugin spec: tolerations: - operator: Exists containers: - name: device-plugin image: registry.example.com/hygon/dcu-device-plugin:latest env: - name: VDCU_MODE value: mixed # 支持整卡和vDCU混布 securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys - name: dev mountPath: /dev volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: dev hostPath: path: /dev这里有几个地方要特别说明。privileged权限是必须的因为插件需要遍历/dev下的设备节点、读取/sys里的设备拓扑信息。VDCU_MODE这个环境变量是海光插件特有的我查了源码才搞明白它支持三种取值disabled只用整卡、timeslice只用时间片、mixed整卡和vDCU共存。如果你是生产环境建议直接配mixed让上层业务自己按需声明资源类型。部署完成后执行kubectl get pods -n kube-system | grep dcu-device-plugin确认插件Pod全部Running然后用kubectl describe node查看节点资源你会发现类似下面的信息Capacity: cpu: 96 memory: 515948Mi hygon.com/dcu: 2 hygon.com/vdcu: 20看到hygon.com/dcu和hygon.com/vdcu这两个自定义资源出现说明设备插件上报成功了。注意这里的vDCU数量不等于实际可用的虚拟卡数量它只是时间片配额的总量调度器按这个数字来分配资源。3.3 整卡与vDCU的Pod声明方式设备插件跑起来之后业务侧只需要在Pod里声明资源就行。整卡模式声明如下apiVersion: v1 kind: Pod metadata: name: dcu-whole-card spec: containers: - name: main image: registry.example.com/ai/base:rocm6.2 imagePullPolicy: IfNotPresent command: [sleep, infinity] resources: limits: hygon.com/dcu: 1这个写法跟声明GPU资源几乎一样只是资源名换了。要注意的是limits和requests最好都写上同样的值否则调度器按requests分配但容器运行时按limits去做设备挂载两者不一致会出现“明明分配了卡但容器里看不到设备”的情况。vDCU模式差别在资源名和数量上。如果是时间片vDCU声明方式是把之前看到的vDCU总配额当成一种可拆分资源去要resources: limits: hygon.com/vdcu: 3这个意思是我要3个时间片配额单元实际运行时由设备插件把某个物理卡的部分时间片分配给我。如果是显存隔离vDCU声明起来稍微复杂一点因为除了数量之外还需要指定显存大小和算力规格。我用的方式是加annotationmetadata: annotations: hygon.com/vdcu-memory: 16Gi hygon.com/vdcu-compute: 25 spec: containers: - name: main resources: limits: hygon.com/vdcu: 1hygon.com/vdcu-memory表示这个虚拟卡要16GB显存hygon.com/vdcu-compute表示算力配比是25%。设备插件在调度时会去匹配空闲物理卡上能否切出满足条件的vDCU分区。如果你不写这两个annotation插件可能会创建默认规格的vDCU跑大模型时大概率显存不够直接OOM。3.4 手工验证设备挂载与ROCm程序运行配置完Pod之后别急着往上跑业务先做一轮基础验证。进入Pod执行rocm-smi或者hy-smi查看系统里能看到几张卡。整卡模式下Pod里看到的应该正好一张物理卡时间片模式下看到的是物理卡但性能特性是共享的显存隔离模式下你会看到一张逻辑卡名字通常带vDCU后缀显存大小就是刚才设置的值。然后跑一个简单的HIP程序验证计算功能。我习惯用rocminfo命令配合一个矩阵乘法小测试如果输出正常说明驱动、ROCm runtime和容器挂载这条链路都是通的。这里有个经验很多情况下设备能被识别但程序一启动就报HSA_STATUS_ERROR_OUT_OF_RESOURCES这种大多是容器缺少/dev/hugepages或者共享内存目录挂载导致的需要在Pod里显式声明hugepage资源。4. CubeStudio平台接入与DeepSeek模型部署4.1 在CubeStudio里配置DCU资源池CubeStudio这类AI平台的核心功能是资源抽象它本身并不直接调用DCU而是把底层的Kubernetes资源封装成“资源池”的概念再提供给上层的Notebook、训练任务、在线推理服务使用。所以平台接入DCU的前提就是刚才三步都完成了驱动装好、设备插件上报资源、K8s能调度DCU资源。在CubeStudio控制台里配置资源池时主要做两件事。第一件是添加资源类型平台通常会读取K8s节点的扩展资源列表你需要把hygon.com/dcu和hygon.com/vdcu识别出来并映射到平台内部的加速卡类型。如果平台的下拉框里只有GPU选项可以先选GPU然后在高级配置里改成自定义资源名。第二件是设置资源池的调度策略比如整卡池和vDCU池的配额、是否允许超卖、按什么策略抢占等。我建议在平台里建三个资源池对应三种模式dcu-whole、dcu-vdcu-time、dcu-vdcu-mem。这样用户创建任务时能直接按业务需求选择平台在后台生成K8s Pod时会把对应的资源声明和annotation带上避免用户自己写YAML时填错。资源池建好之后平台会做一次资源校验确认各池的实际可用数量。如果某个池显示可用数量为0大概率是annotation或者资源名配置不对回到K8s层去排查平台侧通常是读不到实际设备状态的。4.2 创建vDCU虚拟化资源池的注意事项配置vDCU池和整卡池有个本质区别整卡池只需要关注数量vDCU池还要关注切分配置。在CubeStudio里创建vDCU池时我踩了一个印象很深的坑。平台的创建表单里有一个“单卡虚拟化数量”的字段默认值是4。我按默认值建了池结果提交任务时一直报“无法分配vDCU资源”。排查了很久最后发现是因为我那张DCU卡的最小切分规格是8GB按当前卡的显存算最多只能切3个vDCU而我把虚拟化数量设成4设备插件尝试创建第4个vDCU时直接失败导致整个池子的资源上报都异常。所以配置这个字段前一定先查物理卡的显存大小和最小切分粒度这两个数字一除才是合理的虚拟化数量上限。另一个注意事项是临时vDCU池的回收策略。vDCU资源跟整卡不同它是在任务提交时动态创建、任务结束时释放的。如果平台设置了过长的资源保留时间任务结束但虚拟卡还占着显存物理卡的有效算力会被慢慢蚕食。我的习惯是让平台的资源回收周期和K8s的Pod优雅退出时间保持一致任务结束立即释放vDCU。4.3 DeepSeek模型在DCU上的部署流程DeepSeek模型部署这块我用的是vLLM作为推理引擎。vLLM是目前兼容性做得比较好的推理框架之一对ROCm生态也有官方支持在海光DCU上跑DeepSeek基本不需要改代码把GPU相关的环境变量切成ROCm就行。先拉一个装好ROCm依赖和vLLM的镜像然后启动推理服务。以下是我在整卡模式下部署DeepSeek-R1-Distill-Qwen-7B的启动命令vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-7B \ --served-model-name deepseek-r1-7b \ --tensor-parallel-size 1 \ --max-model-len 16384 \ --gpu-memory-utilization 0.9 \ --trust-remote-code这里解释一下几个参数的选择逻辑。tensor-parallel-size设为1是因为7B模型单张卡能放下不需要跨卡并行如果你的模型是32B或者更大单张卡放不下就得把这个参数调大设备插件会帮你分配多张整卡。max-model-len设成16384是权衡了上下文长度和显存占用之后的取值超过这个长度会导致KV cache把显存占满触发OOM。gpu-memory-utilization设0.9是给renderer和tokenizer留了一点余量跑满0.99容易在并发高点时爆显存。服务启动后用请求验证一下curl http://localhost:8000/v1/completions \ -H Content-Type: application/json \ -d {model:deepseek-r1-7b,prompt:用三句话介绍Kubernetes重点说明它对AI平台的价值。,max_tokens:512}正常情况下会返回带choices字段的JSON。注意如果返回报错说找不到CUDA说明vLLM没启用ROCm后端需要检查镜像里是否安装了torch的ROCm版本而不是CPU版本这是最容易被忽略的一个点。4.4 在不同vDCU模式下部署DeepSeek的差异把DeepSeek部署在整卡上和部署在vDCU上模型侧基本不用改变化主要在资源声明和性能表现。时间片vDCU模式下部署DeepSeek我建议直接把上面的启动命令原样拿过来只是K8s的Pod声明里把资源从hygon.com/dcu: 1换成hygon.com/vdcu: 3。但要做好心理准备如果同一张物理卡上同时跑了其他任务你的首token延迟和生成速度都会波动。实测下来并发高峰时吞吐可能掉到整卡模式的40%到60%但如果集群利用率本来就不高这种模式能把闲置算力变成额外收益。显存隔离vDCU模式下部署DeepSeek最需要注意的是gpu-memory-utilization参数。因为vDCU分区里的显存是固定的比如你切了一个16GB的虚拟卡在Pod里看来就是一张16GB显存的卡。如果你还是用显存利用率0.9的默认逻辑vLLM会尝试预留14.4GB给KV cache但模型权重本身可能就要占十几个GB两者相加直接超限。我的做法是先把gpu-memory-utilization调低到0.5到0.6跑一遍看模型能否加载再慢慢调高找到一个稳定又不爆显存的值。另外显存隔离vDCU模式下DeepSeek多副本部署很方便。一个整卡64GB的物理卡切成3份每个20GB可以跑三个小模型的推理副本每个副本独立服务不同业务部门互不干扰。这在企业里非常实用让研发、测试、生产各占一个vDCU互不抢资源也不用为每个环境单独采购物理卡。5. 常见问题与排查技巧实录5.1 设备插件正常但节点没有DCU资源这是我最常被问到的问题。现象设备插件Pod Running但kubectl describe node看不到hygon.com/dcu的资源。排查思路分三步。第一步看设备插件的日志重点看有没有“注册成功”的字样。如果插件启动时报告socket连接失败检查Device Plugin的socket目录是否和kubelet一致。第二步看kubelet的日志看它有没有把插件上报的资源写进节点状态。如果kubelet一直报invalid device plugin socket大概率是插件Pod挂载的hostPath和kubelet实际监听的sock文件位置对不上。第三步看/var/lib/kubelet/device-plugins/下有没有名为hygon-vdcu.sock之类的文件没有这个文件说明插件根本没成功启动注册流程。调这个问题的过程里我有两个实操结论一是设备插件必须以DaemonSet方式运行不能只部署一个Pod否则只有被调度的那个节点有资源二是如果你改了kubelet的feature-gates比如启用了什么新特性最好同时检查设备插件版本是否匹配旧版插件在新版K8s上偶尔会出现注册协议不兼容的问题。5.2 vDCU资源显示数量充足但任务调度失败这个问题通常出现在显存隔离vDCU模式下。现象节点上vDCU资源看着一堆但提交任务后Pod一直Pending事件提示0/3 nodes are available。原因在于vDCU资源是“逻辑配额”不是“实体资源”。设备插件上报的vDCU总数是按照某种默认规则计算的比如按照能切多少个最小规格的虚拟卡来算。但你声明一个需要16GB显存的vDCU时调度器只看数字发现总数够就把Pod调度过去了。等设备插件真正尝试在节点上创建vDCU时发现物理卡的显存剩余不够于是拒绝分配反馈给kubeletPod就一直Pending。解决办法有两个。一是给任务加上调度约束用nodeSelector或者affinity把任务固定到有足够大显存的节点二是调整设备插件上报的资源总量让它不要按最低规格上报而是按实际可分配情况报虽然节点的vDCU数量看起来变少了但每个都能真用。5.3 DeepSeek推理任务启动报显存不足这个问题的典型场景是Pod已经跑起来了但vLLM启动过程中日志报CUDA out of memory或者hipMalloc failed。原因基本有两个。第一个是上面提到的gpu-memory-utilization设置过高模型权重加载完就没多少空间留给KV cache。第二个是Pod容器里有一些额外进程占用了显存比如数据预处理时用了带GPU的库或者你使用了模型并行但参数没写对。排查时进入容器用hy-smi查看显存占用能清楚看到是哪个进程占的。我建议在看日志之前先做一次显存规划模型权重大概占多少GBKV cache需要多少GB最大并发token数是多少三者相加再加10%的余量才是你需要的vDCU显存大小。如果规划出来发现显存不够优先考虑降并发和max-model-len而不是换更大的卡。5.4 同一物理卡上的vDCU任务互相影响明显时间片vDCU模式下如果同卡任务彼此影响明显比如一台跑批处理的任务把算力拉满另一台的推理延迟暴涨了几倍这是软隔离的天然缺陷不是配置错误。缓解办法可以从两个层面入手。应用层给训练类任务和推理类任务分开建立vDCU池不要让它们在同一个时间片池里混跑。调度层设置资源配额上限限制单个任务能占用的vDCU数量避免一个任务把池子配额全部占满。如果业务对延迟要求很高就要考虑把推理服务迁到显存隔离vDCU池里牺牲一点配置复杂度换取稳定的性能边界。5.5 常见问题速查表问题现象可能原因排查方法节点无DCU资源设备插件socket目录不匹配检查kubelet和插件挂载的device-plugins目录Pod Pending但vDCU配额充足物理卡显存不足以创建vDCU查看设备插件日志确认创建vDCU失败原因容器内看不到DCU设备Pod未声明DCU资源检查resources.limits是否包含hygon.com/dcuvLLM报找不到设备torch装成了CPU版本检查镜像内torch是否为ROCm版本推理服务偶发OOMgpu-memory-utilization过高调低参数预留显存余量同卡任务互相干扰时间片软隔离拆分资源池或改用显存隔离vDCU这次适配海光DCU到Kubernetes和CubeStudio的整个过程回头看其实没有哪个环节是真正“卡脖子”的更多的是生态不够成熟导致文档分散、坑位要靠自己踩。比如设备插件的VDCU_MODE变量官方文档里没有细说源码看一眼才明白又比如显存隔离vDCU的切分规格限制驱动日志不放大看根本发现不了问题。但把整条链路跑通之后收获也是实打实的集群里每张物理卡的利用率从原来的不到20%拉到了70%以上不同团队可以安全地共享同一批算力DeepSeek这类开源大模型也顺利地跑在了国产DCU平台上。根据我个人经验做这类适配一定要按“驱动到插件、插件到平台、平台到业务”的顺序逐层验证每一层都确认没问题再往下走否则多个环节同时出问题时排查起来会特别痛苦。