Daft 多模态视频抽帧性能优化实践:从抽帧到大模型打标,一条视频理解流水线是怎么跑起来的
Daft 夿¨¡æè§é¢æ½å¸§æ§è½ä¼åå®è·µï¼ä»æ½å¸§å°å¤§æ¨¡åææ ï¼ä¸æ¡è§é¢çè§£æµæ°´çº¿æ¯æä¹è·èµ·æ¥ç
é¿éäºå¤§æ°æ®AIææ¯ 2026-08-18 0 é 读11åéåè¨
è¿å»ä¸¤å¹´ï¼å¤æ¨¡æå¤§æ¨¡åçè½åè·å让"æè§é¢åç»æ¨¡åï¼è®©å®çæç»é¢éåçäºä»ä¹"æä¸ºè¶æ¥è¶å¤ç¨æ·çå®é éæ±ï¼ä¸æ¤åæ¶ï¼çæ§å½åãç´æåæ¾ãUGC å 容ãå·¥ä¸è´¨æ£å½±åçåºæ¯äº§ççè§é¢æ°æ®å¨è¾ä»¥Â TB 计ãè¦æè¿äºæ°æ®åæå¯æ£ç´¢ãå¯åæçç»æåä¿¡æ¯ï¼"è§é¢æ½å¸§Â + 大模åçè§£"æäºæç´æ¥ç路线ã
Daft æ¯ä¸ä¸ªé¢å夿¨¡ææ°æ®ç弿ºåå¸å¼Â DataFrameÂ å¼æï¼åçæ¯æå¾çãè§é¢çéç»æåæ°æ®ç读åå忢ã卿¤åºç¡ä¸ï¼EMR Serverless Spark å¢éæ¨åºäºæ·±åº¦ä¼åç DaftÂ çæ¬ï¼ä¸ä»
大å¹
æåäºè§é¢ãå¾çç夿¨¡ææ°æ®çè¯»åæ§è½ï¼è¿æä¾äºÂ ai_query ç大模åè°ç¨åè¯å丰å¯çå
ç½®ç®åï¼è¦çææ¬æ¸
æ´ãå¾åå¤çãæ ¼å¼è½¬æ¢çï¼ï¼è®©"æ°æ®è¯»å â é¢å¤ç â AI çè§£"å¨ä¸æ¡Â DataFrameÂ æµæ°´çº¿ééç¯ãæä»¬åºäºè¿ä¸ªçæ¬æå»ºäºä¸æ¡ä»è§é¢å°ç»æåæ ç¾ç宿´ç®¡çº¿ï¼å¹¶å¨å®é
çäº§ä¸æå®æç£¨å°äºå¯ç¨çæ§è½æ°´å¹³ãè¿ç¯æç« ä¼é¡ºçæ°æ®çæµå¨ï¼æè¿æ¡ç®¡çº¿ä»å¤´å°å°¾èµ°ä¸éã
è§é¢çè§£ï¼æ£å¨æä¸ºæ°æ®ç®¡çº¿çæ é
å å®¹å®¡æ ¸è¦å¤æç»é¢æ¯å¦åè§ï¼è§é¢ææ è¦ç»æ¯ä¸å¸§çææè¿°åæ ç¾ï¼ç´æè´¨æ£è¦æåå¼å¸¸ç段ï¼å®é²åçè¦æ£ç´¢ç¹å®åºæ¯ââè¿äºéæ±èå齿¯å䏿¡æµæ°´çº¿ï¼æè§é¢ææä¸å¸§å¸§å¾çï¼äº¤ç»å¤§æ¨¡åçè§£ï¼åæç»æç»æåå ¥åºã
å çä¸ä¸ªå®æ´çä¾åãå设 OSS çæä¸ªç®å½ä¸æä¸æ¹è§é¢ï¼æ¯ä¸ªé½æ¯ä»¥Â GB 计ç大æä»¶ï¼æä»¬åªå ³å¿æ¯ä¸ªè§é¢ç¬¬Â 10 åéä¹åçå 容ï¼å¸ææ¯Â 10Â ç§æ½ä¸å¸§ï¼æå¸§å¾çåå°Â OSSï¼å让大模åç»æ¯å¸§çææè¿°æ ç¾ï¼
from pathlib import PurePosixPath
import daft
from daft import DataType, col, lit
from daft.emr.functions import ImageBlackBorderCrop, ai_query, emr_udf
from daft.io import IOConfig, JindoConfig
io_config = IOConfig(
jindo=JindoConfig(
endpoint="oss-<your-region>-internal.aliyuncs.com",
access_key_id="<your-ak>",
access_key_secret="<your-sk>",
)
)
@daft.func(return_dtype=DataType.string())
def make_image_name(path: str, frame_index: int) -> str:
"""æ <è§é¢å>/frame_<帧å·> å½åï¼é¿å
ä¸åè§é¢ç帧混å¨ä¸èµ·ã"""
video_name = PurePosixPath(str(path).split("?")[0]).stem
return f"{video_name}/frame_{int(frame_index):06d}"
frames_df = (
daft.read_video_frames(
path="oss://my-bucket/videos/*.mp4", # ç®å½ä¸ææè§é¢
image_height=480,
image_width=640,
start_time=600.0, # åªä»ç¬¬ 10 åéå¼å§è¯»
sample_interval_seconds=10.0, # æ¯ 10 ç§åä¸å¸§
decode_thread_type="AUTO", # å¤çº¿ç¨è§£ç
decode_thread_count=4,
io_config=io_config,
)
# å»é¤é»è¾¹å¹¶åå
¥ OSSï¼è¿å struct{base64, image_path}ï¼å image_path ä½ä¸º OSS è·¯å¾ï¼
.with_column(
"crop_result",
emr_udf(
ImageBlackBorderCrop,
construct_args={
"image_src_type": "image_ndarray",
"detect_algorithm": "auto",
"crop_sides": ["top", "bottom"],
"output_dir": "oss://my-bucket/frames/",
"image_format": "jpg",
"quality": 85,
"io_config": io_config,
},
concurrency=4,
batch_size=32,
)(image=col("data"), image_name=make_image_name(col("path"), col("frame_index"))),
)
.with_column("image_url", col("crop_result").get("image_path"))
# 让大模åç»æ¯ä¸å¸§ææ
.with_column(
"ai_result",
ai_query(
lit(
"请æè¿°è¿å¼ ç»é¢çå
容ï¼å
æ¬åºæ¯å¸å±ã人ç©å¨ä½ãç¯å¢ç¹å¾ã"
"æ´ä½æ°å´ãè¾åº JSONï¼å
å« scene_descriptionãtagsï¼æ°ç»ï¼ã"
"mood ä¸ä¸ªå段ã"
),
data=col("image_url"),
data_type="uri",
),
)
# ai_result æ¯å°è£
ç»æä½ï¼æ¨¡åçä¸å¡è¾åºå¨ content åæ®µé
.with_column("content", col("ai_result").get("content"))
)
# å°æ¹ééªè¯ï¼collect() æç»æä¸æ¬¡æ§ç©ååºæ¥
results = frames_df.select(
col("path"), col("frame_time"), col("image_url"), col("content")
).collect()
ççå åè¡ï¼ä¸æ¡"è§é¢Â â å¾ç â AIÂ æ æ³¨Â âÂ ç»æåç»æ"ç管线就æåäºãä¸é¢æä»¬é¡ºçæ°æ®çæµå¨ï¼ççæ¯ä¸æ¥åçäºä»ä¹ã
ç¬¬ä¸æ¥ï¼æè§é¢è¯»è¿æ¥ï¼ä½åªè¯»éè¦çé¨å
æµæ°´çº¿çå
¥å£æ¯Â read_video_framesã宿¥åä¸ä¸ªÂ OSS 路å¾ï¼ç´æ¥ä»å¯¹è±¡åå¨è¯»åè§é¢ï¼ä¸éè¦ä½ å
æä¸ä¸ªå¤§æä»¶ä¸è½½å°æ¬å°ââè¿ç¨è¯»åãæ ¼å¼è§£æãè§£ç è¿äºèæ´»é½ç±æ¡æ¶æ¥ç®¡ã
ä½"读è§é¢"è¿ä¸æ¥æ°æ°æ¯æ´æ¡ç®¡çº¿éæå®¹æè¢«ä½ä¼°çç¶é¢ãè§é¢è§£ç æ¯Â CPU å¯éåæä½ï¼ä¸æ®µÂ 20 åéã30fps çè§é¢æÂ 36000 帧ï¼å¦æä»å¤´å°å°¾é帧顺åºè§£ç ï¼å è§£ç å°±è¦è±è´¹å¾é¿æ¶é´ã
è¿éèçæä»¬åç第ä¸ç±»ä¼åï¼è¯»åæ¶çæé䏿¨ãä¸å¡å¾å¾åªå
³å¿è§é¢çä¸é¨åââä¸é¢çä¾åéæä»¬åªè¦ç¬¬Â 10 åéä¹åçå
容ãéè¿Â start_time æè¿ä¸ªä¿¡æ¯ä¸æ¨å°è¯»åæºï¼Daft ä¼å©ç¨è§é¢å®¹å¨çå
³é®å¸§ç´¢å¼ç´æ¥å®ä½å°ç®æ ä½ç½®éè¿å¼å§è§£ç ï¼è䏿¯ä»ç¬¬Â 0 ç§é¡ºåºè§£å°ç¬¬Â 10 åéãå 600 ç§çè§£ç å·¥ä½è¢«æ´æ®µè·³è¿ï¼å¯¹äºè¿ç¨å¤§æä»¶ï¼ç½ç»Â IO ä¹ä¸å¿
åä»å¤´æåæ´ä¸ªæä»¶åç¼ã
å¼å¾ä¸æçæ¯ï¼è¿ç§è¯»åæ¯èªå¨åå¸å¼çãä¸é¢çä¾åéï¼path ç¨äºéé
符å¹é
æ´ä¸ªç®å½ï¼å½ç®å½ä¸æå åä¸ç¾ä¸ªè§é¢æ¶ï¼Daft ä¼èªå¨ææ¯ä¸ªè§é¢çè§£ç ãè£è¾¹ãä¸ä¼ ãæ¨¡åè°ç¨ææç¬ç«ä»»å¡ï¼åé
å°Â Ray é群çå¤ä¸ªÂ worker ä¸å¹¶è¡æ§è¡ï¼å个è§é¢çå¸§æ°æ®éè¾å¤§æ¶ï¼è¿ä¼è¢«è¿ä¸æ¥åæå¤ä¸ªååºãä½ ä¸éè¦åä»»ä½åå¸å¼ä»£ç ï¼ä¹ä¸éè¦å
³å¿èµæºæä¹ååã任塿ä¹è°åº¦ââè¿äºå
¨é¨ç±æ¡æ¶æ¥ç®¡ã
ç¬¬äºæ¥ï¼éæ ·ï¼åªä¿çæä»·å¼ç帧
è§é¢è¯»è¿æ¥ä¹åï¼ä¸å¡çæ£éè¦çé叏䏿¯æ¯ä¸å¸§ï¼èæ¯æåºå®èå¥éæ ·çå
³é®å¸§ãsample_interval_seconds=10 åè¯Â Daftï¼æ¯Â 10 ç§ç»æä¸å¸§å°±å¤äºã
éæ ·çä¼¼åªæ¯"æ¯éå ç§æä¸å¸§"ï¼ä½æ´ç´ å®ç°éèç大鿵ªè´¹ã30fps çè§é¢ï¼10 ç§é´éæå³çä¸¤ä¸ªéæ ·ç¹ä¹é´æÂ 300 帧ï¼å ¶ä¸Â 299 帧æ¯è¦è¢«ä¸¢æçãå¦æé¡ºåºè§£ç ï¼è¿Â 299Â å¸§å ¨é½è¢«å®æ´è§£åºæ¥åç«å»ææï¼CPU ç½ç½ç©ºè½¬ã
è¿å°±æ¯æä»¬åç第äºç±»ä¼åï¼è·³è¿ä¸éè¦ç帧ãå½éæ ·é´éè¶³å¤å¤§æ¶ï¼Daft å¨è¾åºä¸å¸§ä¹åä¼ç´æ¥Â seek å°ä¸ä¸ä¸ªéæ ·ç¹éè¿ï¼è·³è¿ä¸é´é£å ç¾å¸§çè§£ç ãè§£ç å¨åªç¢°å®çæ£è¦è¾åºçé£äºå¸§ï¼æ"è§£åºæ¥å丢æ"åæ"æ ¹æ¬ä¸è§£"ãè¿é¡¹ä¼å对å¯å帧çï¼VFRï¼è§é¢åæ ·ææï¼è®©ææºå½åãç´æåæ¾è¿ç±»è§é¢ä¹è½åçã
䏿¤åæ¶ï¼å©ä¸é£äºçæ£è¦è§£ç ç帧ï¼ä¹ä¸è¯¥åçº¿ç¨æ
¢æ
¢åãdecode_thread_type å decode_thread_count 让åºå±è§£ç å¨ç¨å¤ä¸ªçº¿ç¨å¹¶è¡å·¥ä½ï¼æå¤æ ¸Â CPUÂ ç¨æ»¡ãè¿æ¯æä»¬å¨è§£ç ç¯èåç第ä¸ç±»ä¼åï¼åéæ ·Â seek é
åï¼è®©"å°è§£Â + 快解"å å çæã
ç¬¬ä¸æ¥ï¼ç»é¢é¢å¤çââ廿é»è¾¹ï¼ç´æ¥è½ç
éæ ·åºæ¥ç帧è¿ä¸è½ç´æ¥äº¤ç»å¤§æ¨¡åãçå®åºæ¯çè§é¢ç»é¢ç»å¸¸å¸¦æé»è¾¹ââå½å¶è®¾å¤çç»å¹ æ¯ä¾åè¾åºå辨çä¸å¹é æ¶ï¼ç»é¢ä¸ä¸æå·¦å³å°±ä¼åºç°æ æä¹çé»è²æ¡å¸¦ãè¿äºé»è¾¹ä¸ä» 浪费åå¨ç©ºé´ï¼è¿ä¼å¹²æ°å¤§æ¨¡å对ç»é¢å 容ççè§£ã
EMR Serverless Spark Daft æä¾äºÂ ImageBlackBorderCrop ç®åæ¥å¤çè¿ä¸ªé®é¢ãå®è½èªå¨æ£æµå¹¶è£æç»é¢åå¨çé»è¾¹ï¼æ¯æå¤ç§æ£æµç®æ³ï¼é弿«æãè¾¹ç¼æ£æµãç´æ¹å¾åæï¼ï¼ä¹å¯ä»¥ç¨Â auto 模å¼è®©ä¸ç§ç®æ³æç¥¨åå
±è¯ï¼é¿å
åä¸ç®æ³å¨ç¹å®ç»é¢ä¸è¯¯å¤ãä½ è¿å¯ä»¥éè¿Â crop_sides æå®åªå¤çæå 个æ¹åââæ¯å¦è§é¢åªæé¡¶é¨é»è¾¹ï¼å°±åªè£é¡¶é¨ã
æ´éè¦çæ¯ï¼è¿ä¸ªç®åä¸åªæ¯è£åªãå®åæ¶æ¿æ
äºç¼ç åä¸ä¼ çå·¥ä½ï¼è£åªå®æåç´æ¥æÂ JPEG åå
¥ä½ æå®ç OSS 路å¾ï¼å¹¶è¿åä¸ä¸ªå
å«è¯¥è·¯å¾çç»æä½ï¼{base64, image_path}ï¼ãä½ ä¸éè¦èªå·±å"è£åªÂ â ç¼ç  â ä¸ä¼ "ç䏿¥é»è¾ï¼ä¸ä¸ªÂ emr_udf è°ç¨å
¨é¨æå®ï¼æ¡æ¶å
é¨èªå¨å¤çå¹¶ååæ¹éãååºå
¶ä¸ç image_path 就æ¯å¯è®¿é®ç OSS å¾çè·¯å¾ã
两个è°åº¦åæ°é¡ºå¸¦è¯´æï¼concurrency æå®åæ¶è¿è¡ç UDF å®ä¾æ°ï¼ç¤ºä¾ä¸ä¸ºÂ 4ï¼ï¼batch_size æå®æ¯ä¸ªå®ä¾æ¯æ¹å¤ççè¡æ°ï¼ç¤ºä¾ä¸ä¸ºÂ 32ï¼ï¼ä¸¤è
å
±åå³å®è¿ä¸æ¥çå¤çååï¼å¯ä»¥æÂ CPU åç½ç»å¸¦å®½è°æ´ã
è¿ä¸æ¥ä¹åï¼æ¯ä¸å¸§é½åæäºä¸å¼ å¹²åçãå¯è®¿é®ç OSS å¾ç URLï¼éæ¶å¯ä»¥äº¤ç»ä¸æ¸¸ã
ç¬¬åæ¥ï¼äº¤ç»å¤§æ¨¡åææ
å¾çå°±ä½åï¼ai_query è´è´£è°ç¨å¤§æ¨¡åãå®ä»¥Â DataFrame çä¸åï¼å¾ç URLï¼ä¸ºè¾å
¥ï¼ä½ åªéè¦å䏿®µÂ prompt æè¿°ä½ æ³è¦ä»ä¹ï¼å©ä¸çæ¹é请æ±ãå¹¶åè°åº¦ãç»æè§£æé½ç±æ¡æ¶å¤çã
éè¦æ³¨æçæ¯ï¼ai_resultÂ å¹¶ä¸æ¯æ¨¡åçä¸å¡è¾åºæ¬èº«ï¼èæ¯ä¸ä¸ªå°è£
ç»æä½ï¼é¤äºÂ content éçä¸å¡å
容ï¼è¿å¸¦æÂ finish_reasonãToken ç¨éãé误信æ¯çè¿è¡å
æ°æ®ãæä»¬è®©æ¨¡åè¾åºç scene_descriptionãtagsãmood JSON ä½äºÂ contentÂ åæ®µï¼å符串ï¼ï¼ä¸æ¸¸æéè§£ææç»æååæ®µå³å¯ï¼
# ai_result æ¯å°è£
ç»æä½ï¼æ¨¡åçä¸å¡è¾åºå¨ content åæ®µé
.with_column("content", col("ai_result").get("content"))
å°è¿ä¸æ¥ï¼ç»æå°±å¯ä»¥è¿å
¥ä¸æ¸¸ââç»§ç»Â joinãè¿æ»¤ï¼æåå
¥Â Paimon çç»æè¡¨ã示ä¾éæä»¬ç¨äºÂ collect() æç»æä¸æ¬¡æ§ç©åï¼éåå°æ¹ééªè¯ï¼ç产ç¯å¢å»ºè®®æ¢æå表å¨ä½ã
æ´æ¡ç®¡çº¿å°Â collect() æçæ£å¼å§æ§è¡ã卿¤ä¹åçæ¯ä¸æ¥é½åªæ¯å¨æå»ºæ§è¡è®¡åï¼å¾çäºæ°æ§æ±å¼ï¼è¯»åãéæ ·ãç¼ç ãä¸ä¼ ãæ¨¡åè°ç¨ä¼è¢«ç¼ææä¸æ¡è¿è´¯çæµæ°´çº¿ï¼ä¸é´ç»æä¸éè¦å
¨éç©åå¨å
åéã
æ§è½å®æµï¼ä¸é¡¹ä¼åçå å æ¶ç
è§é¢ç解管线éï¼å¤§å®¶ç注æåå¾å¾é½å¨æ¨¡åè°ç¨ç«¯ï¼ä½å¦æåé¢çè§é¢è§£ç è¿å¨å线ç¨ä»å¤´è¯»å°å°¾ï¼åå¿«çæ¨¡åä¹åªè½å¹²ççãæä»¬å¨è¯»ååéæ ·ç¯èåçä¼åââæé䏿¨ãè·³è¿æ ç¨å¸§ãå¤çº¿ç¨è§£ç ââå å èµ·æ¥æ¶çæå¤å¤§ï¼æä»¬å¨åä¸ä¸ªÂ Ray é群ãåä¸éåä¸åäºÂ A/BÂ å¯¹ç §ï¼ä¸¤ä¸ªçæ¬å¯ä¸çå·®å¼å°±æ¯ä¼åä»£ç æ¬èº«ï¼
æµè¯ç´ ææ¯ä¸æ®µåæ¾å¨Â OSS ä¸ã约 1200 ç§ç VFRÂ çæ§è§é¢ï¼30fpsï¼ï¼æ½ååºæ¯ä¸ºè¯»å第 900~1200 ç§åºé´ãæ¯Â 1Â ç§æ½ä¸å¸§ï¼
| ä¼å项 | Baseline (s) | ä¼åå (s) | å 鿝 |
|---|---|---|---|
| åºé´ä¸æ¨ | 1382 | 322 | 4.30x |
| éæ ·è·³å¸§ | 1335 | 1087 | 1.23x |
| å¤çº¿ç¨è§£ç | 1331 | 789 | 1.69x |
| ä¸é¡¹å ¨é¨å å | 1334 | 121 | 11.05x |
ä¸é¡¹ä¼ååèªç¬ç«ãå ä¹çº¿æ§å å ï¼å å å忬è¦è·Â 22 åéçä»»å¡ï¼2 åé就宿äºã
å卿å
æ§è½åªæ¯å ¶ä¸ä¸é¢ãåè¿å¤´çæç« ä¸ç代ç ï¼è¯»è§é¢ãå»é»è¾¹ãå OSSãè°å¤§æ¨¡åï¼æ¯ä¸æ¥é½æ¯ä¸ä¸ªç°æç彿°è°ç¨ï¼æ²¡æä¸å¤éè¦ä½ æåè§£ç 循ç¯ãç¼ç é»è¾ãä¸ä¼ éè¯æå¹¶åæ§å¶ãè¿ææ¯æä»¬æ´å¸æä¼ éçä»·å¼ââå¤çæ°æ®æ¶ï¼ä½ åºè¯¥æç²¾åè±å¨ä¸å¡æ¬èº«ï¼æ³æ¸ æ¥"è¦ä»æ°æ®éå¾å°ä»ä¹"ï¼è䏿¯èå¨"æä¹æå®é«æå°è¯»è¿æ¥ãæå¼ãåä¼ åºå»"è¿äºåºå±ç»èä¸ã
EMR Serverless Spark Daft ç髿§è½ç®åå AI FunctionÂ æ£æ¯ä¸ºæ¤èçãè§é¢æ½å¸§ãé»è¾¹è£åªãå¾åå¤çãææ¬æ¸ æ´ã大模åè°ç¨â¦â¦è¿äºå¨æ°æ®ç®¡çº¿éåå¤åºç°ãå容æåéåæ ¢çç¯èï¼æä»¬æå®ä»¬æ²æ·æç»è¿ä¼åçãå¼ç®±å³ç¨ç彿°ï¼å¤æåº¦ç卿¡æ¶å é¨ï¼æ§è½ãå¹¶åã容éç±æä»¬å åºï¼ä½ åªéè¦åæç§¯æ¨ä¸æ ·æå®ä»¬ä¸²æèªå·±ç管线ã
å»é»è¾¹åªæ¯å°å±±ä¸è§ãEMR Serverless Spark Daft å ç½®äºä¸ä¸ªæç»æ©å çç®åå¸åºï¼è¦çå¾åãè§é¢ãææ¬ãé³é¢ç夿¨¡ææ°æ®ç常è§å¤çåºæ¯ï¼æ"æ°æ®è¯»å â é¢å¤ç â AI çè§£"åç¯èåå¤åºç°çèæ´»ç´¯æ´»é½æ²æ·æäºå¼ç®±å³ç¨ç彿°ãä¸é¢è¿å¼ å¾å举äºå ¶ä¸ä¸é¨åç®åï¼
å¯ç¨æ§è¯´æï¼æ¬æä»ç»ç AI Function ç®åå¤äºéæµé¶æ®µï¼å°æªæ£å¼å¼æ¾ãå¦éæ¢å ä½éªï¼è¯·è系 EMR Serverless Spark å¢éç³è¯·ç½ååãéé群å·ï¼58570004119ã
Aitishiku.com