[譯] TensorFlow 白皮書

Neil Zhu嫉戚,簡(jiǎn)書ID Not_GOD对嚼,University AI 創(chuàng)始人 & Chief Scientist机蔗,致力于推進(jìn)世界人工智能化進(jìn)程背传。制定并實(shí)施 UAI 中長(zhǎng)期增長(zhǎng)戰(zhàn)略和目標(biāo),帶領(lǐng)團(tuán)隊(duì)快速成長(zhǎng)為人工智能領(lǐng)域最專業(yè)的力量谷婆。
作為行業(yè)領(lǐng)導(dǎo)者慨蛙,他和UAI一起在2014年創(chuàng)建了TASA(中國(guó)最早的人工智能社團(tuán)), DL Center(深度學(xué)習(xí)知識(shí)中心全球價(jià)值網(wǎng)絡(luò))辽聊,AI growth(行業(yè)智庫(kù)培訓(xùn))等,為中國(guó)的人工智能人才建設(shè)輸送了大量的血液和養(yǎng)分期贫。此外跟匆,他還參與或者舉辦過各類國(guó)際性的人工智能峰會(huì)和活動(dòng),產(chǎn)生了巨大的影響力通砍,書寫了60萬(wàn)字的人工智能精品技術(shù)內(nèi)容玛臂,生產(chǎn)翻譯了全球第一本深度學(xué)習(xí)入門書《神經(jīng)網(wǎng)絡(luò)與深度學(xué)習(xí)》,生產(chǎn)的內(nèi)容被大量的專業(yè)垂直公眾號(hào)和媒體轉(zhuǎn)載與連載封孙。曾經(jīng)受邀為國(guó)內(nèi)頂尖大學(xué)制定人工智能學(xué)習(xí)規(guī)劃和教授人工智能前沿課程迹冤,均受學(xué)生和老師好評(píng)。

TensorFlow 從名稱上看就是兩個(gè)部分——張量 tensor 和流 flow虎忌。非常形象的組合泡徙。眾所周知,矩陣已經(jīng)成為機(jī)器學(xué)習(xí)中的基礎(chǔ)單元膜蠢,若干的針對(duì)矩陣的計(jì)算優(yōu)化使得現(xiàn)如今的機(jī)器學(xué)習(xí)成為可能堪藐。而一些矩陣的方法也是一些重要的機(jī)器學(xué)習(xí)算法的基礎(chǔ)。張量 就是矩陣概念的推廣挑围,其表示更多維度的矩陣礁竞。而計(jì)算流是一種抽象過程,在如今的深度學(xué)習(xí)領(lǐng)域贪惹,這種一層層地計(jì)算可以很形象地看做是張量在計(jì)算模型上的流動(dòng)苏章。而這里的流可以看做是更加一般的計(jì)算過程,可以在不同的層級(jí)間跨越式流動(dòng)奏瞬。

本文作者均來(lái)自 Google Research:Martín Abadi, Ashish Agarwal, Paul Barham, Eugene Brevdo, Zhifeng Chen, Craig Citro, Greg S. Corrado, Andy Davis, Jeffrey Dean, Matthieu Devin, Sanjay Ghemawat, Ian Goodfellow, Andrew Harp, Geoffrey Irving, Michael Isard, Yangqing Jia, Rafal Jozefowicz, Lukasz Kaiser, Manjunath Kudlur, Josh Levenberg, Dan Mane, Rajat Monga, Sherry Moore, Derek Murray, Chris Olah, Mike Schuster, Jonathon Shlens, Benoit Steiner, Ilya Sutskever, Kunal Talwar, Paul Tucker, Vincent Vanhoucke, Vijay Vasudevan, Fernanda Viegas, Oriol Vinyals, Pete Warden, Martin Wattenberg, Martin Wicke, Yuan Yu, and Xiaoqiang Zheng

摘要

TensorFlow [1] 是一個(gè)表達(dá)機(jī)器學(xué)習(xí)算法的接口,并且是執(zhí)行算法的實(shí)現(xiàn)框架泉孩。使用 TensorFlow 表示的計(jì)算可以在眾多異構(gòu)的系統(tǒng)上方便地移植硼端,從移動(dòng)設(shè)別如手機(jī)或者平板電腦到成千的GPU計(jì)算集群上都可以執(zhí)行。該系統(tǒng)靈活寓搬,可以被用來(lái)表示很多的算法包括珍昨,深度神經(jīng)網(wǎng)絡(luò)的訓(xùn)練和推斷算法,也已經(jīng)被用作科研和應(yīng)用機(jī)器學(xué)習(xí)系統(tǒng)在若干的計(jì)算機(jī)科學(xué)領(lǐng)域或者其他領(lǐng)域中句喷,例如語(yǔ)言識(shí)別镣典、計(jì)算機(jī)視覺、機(jī)器人唾琼、信息檢索兄春、自然語(yǔ)言理解、地理信息抽取和計(jì)算藥物發(fā)現(xiàn)锡溯。該論文描述了 TensorFlow 的接口和我們?cè)?Google 構(gòu)建的結(jié)構(gòu)實(shí)現(xiàn)赶舆。TensorFlow API 和參考實(shí)現(xiàn)都已經(jīng)作為開源項(xiàng)目按照 Apache 2.0 協(xié)議在 2015 年 11 月釋出哑姚,可以在 ?這里 查看。

1 引言

Google 大腦項(xiàng)目開始于 2011 年芜茵,目的是探索在科研和 Google 的產(chǎn)品中超大規(guī)模深度神經(jīng)網(wǎng)絡(luò)的使用叙量。作為這個(gè)項(xiàng)目的早期工作,我們構(gòu)建了 DistBelief ——第一代的可擴(kuò)展分布式訓(xùn)練和推斷系統(tǒng) [14]九串,這個(gè)系統(tǒng)工作得很不錯(cuò)绞佩。我們和其他 Google 的同事使用 DistBelief 進(jìn)行了廣泛的研究包括非監(jiān)督學(xué)習(xí)[31]、語(yǔ)言表示[35,52]猪钮、圖像分類模型和目標(biāo)檢測(cè)[16,48]品山、視頻分類[27]、語(yǔ)音識(shí)別[56,21,20]躬贡、序列預(yù)測(cè)[47]谆奥、Go 的移動(dòng)選擇[34]、?行人檢測(cè)[2]拂玻、強(qiáng)化學(xué)習(xí)[38] 等等酸些。另外,和 Google Brain 團(tuán)隊(duì)合作中檐蚜,超過 50 個(gè) Google 內(nèi)部的團(tuán)隊(duì)和其他 Alphabet 公司也已經(jīng)部署了使用 DistBelief 的深度神經(jīng)網(wǎng)絡(luò)在眾多產(chǎn)品中魄懂,包括 Google Search[11]、廣告產(chǎn)品闯第、語(yǔ)音識(shí)別系統(tǒng)[50,6,46]市栗、Google Photos[43]、Google Maps 和 街景[19]咳短、Google 翻譯[18]填帽、Youtube 和很多其他的產(chǎn)品。

基于我們使用 DistBelief 的經(jīng)驗(yàn)和對(duì)于期望用來(lái)訓(xùn)練和使用神經(jīng)網(wǎng)絡(luò)的系統(tǒng)特性和需求更加完備地理解咙好,我們構(gòu)建了 TensorFlow——第二代大規(guī)模機(jī)器學(xué)習(xí)模型的實(shí)現(xiàn)和部署的系統(tǒng)篡腌。TensorFlow 使用通過類似數(shù)據(jù)流模型的計(jì)算,將這些計(jì)算映射到不同的硬件平臺(tái)例如使用包含一個(gè)或者多個(gè) GPU 顯卡的裝有 Android 和 iOS 的單個(gè)機(jī)器上進(jìn)行推斷勾效,到運(yùn)行在數(shù)百臺(tái)包含數(shù)千個(gè) GPU 的大規(guī)模系統(tǒng)訓(xùn)練推斷嘹悼。擁有一個(gè)單一的系統(tǒng)可以擴(kuò)展分布到眾多的平臺(tái)上可以大大簡(jiǎn)化真實(shí)場(chǎng)景中機(jī)器學(xué)習(xí)系統(tǒng)的使用,正如我們?cè)谟梅蛛x的系統(tǒng)進(jìn)行大規(guī)模訓(xùn)練和小規(guī)模的部署层宫,會(huì)產(chǎn)生巨大的維護(hù)代價(jià)和較差的抽象效果杨伙。TensorFlow 的計(jì)算被表示為含狀態(tài)的數(shù)據(jù)流圖(在第二節(jié)詳細(xì)講解),我們聚焦在讓這個(gè)系統(tǒng)足夠靈活能夠快速地實(shí)驗(yàn)研究中產(chǎn)生的新模型萌腿,并同時(shí)充分地提升產(chǎn)品級(jí)訓(xùn)練的性能和部署機(jī)器學(xué)習(xí)模型健壯性限匣。為擴(kuò)展神經(jīng)網(wǎng)絡(luò)訓(xùn)練搞更大的部署環(huán)境,TensorFlow 允許 client 簡(jiǎn)單地表達(dá)不同類型的并行通過復(fù)制和并行執(zhí)行一個(gè)核心模型數(shù)據(jù)流圖哮奇,依賴不同計(jì)算設(shè)備合作更新一個(gè)共享的參數(shù)或者其他的狀態(tài)膛腐。 對(duì)計(jì)算描述的微妙變動(dòng)可以使用較低的代價(jià)來(lái)達(dá)到和嘗試很多不同的并行的方法睛约。一些 TensorFlow 的用途借助參數(shù)更新的一致性來(lái)實(shí)現(xiàn)靈活性,我們可以在一些更大的部署環(huán)境中輕易表達(dá)和利用這些同步上的松弛哲身。對(duì)比 DistBelief辩涝,TensorFlow 的編程模型更加靈活,性能也更好勘天,支持在大規(guī)模的異構(gòu)硬件平臺(tái)上訓(xùn)練和使用很多的模型怔揩。

DistBelief 的內(nèi)部用戶已經(jīng)切換成 TensorFlow 了。這些客戶依賴 TensorFlow 來(lái)研究和產(chǎn)品脯丝,執(zhí)行諸如在移動(dòng)電話計(jì)算機(jī)視覺模型的推斷到使用數(shù)百臺(tái)機(jī)器進(jìn)行千億級(jí)樣本的千億級(jí)參數(shù)的深度神經(jīng)網(wǎng)絡(luò)的訓(xùn)練[11,47,48,18,53,41]商膊。盡管這些應(yīng)用集中在機(jī)器學(xué)習(xí)和深度神經(jīng)網(wǎng)絡(luò)上,我們希望 TensorFlow 的抽象可以用在其他的領(lǐng)域中宠进,例如其他的機(jī)器學(xué)習(xí)算法或者可能其他類型的數(shù)值計(jì)算晕拆。我們按照 Apache 2.0 協(xié)議在 2015 年 11 月開源了 TensorFlow API,可以在 www.tensorflow.org 查看材蹬。

本文下面的部分更加細(xì)致地描述了 TensorFlow实幕。第二節(jié)介紹編程模型和 TensorFlow 接口的基本概念,第三節(jié)介紹單機(jī)和分布式的實(shí)現(xiàn) 堤器。第四節(jié)給出了基本編程模型的擴(kuò)展昆庇,第五節(jié)介紹了一些基本實(shí)現(xiàn)的優(yōu)化方法。第六節(jié)給出了一些使用 TensorFlow 的實(shí)驗(yàn)結(jié)果闸溃,第七節(jié)描述了一些使用 TensorFlow 編程的 idiom整吆,第九節(jié)則是一些在 TensorFlow 核心外圍的工具。第十節(jié)和第十一節(jié)分別討論了未來(lái)和相關(guān)的工作辉川,最后一節(jié)給出了總結(jié)性想法表蝙。

2 編程模型和基本概念

TensorFlow 的計(jì)算由一個(gè)有向圖描述,這個(gè)圖中由一個(gè)節(jié)點(diǎn)集合組成乓旗。該圖表達(dá)了數(shù)據(jù)流計(jì)算勇哗,作出了一些類型的節(jié)點(diǎn)保持和更新持久狀態(tài)和讓分支及循環(huán)控制結(jié)構(gòu)類似于 Naiad 的行為方式的擴(kuò)展〈缙耄客戶一般都會(huì)使用 TensorFlow 支持的前端語(yǔ)言(C++或者Python)構(gòu)建一個(gè)計(jì)算圖。在圖 1 中展示了一段樣例使用 Python 構(gòu)建并執(zhí)行了一個(gè) TensorFlow 的計(jì)算圖抄谐,結(jié)構(gòu)計(jì)算圖在圖 2 中展示渺鹦。

圖 1
圖 2

在一幅 TensorFlow 圖中,每個(gè)節(jié)點(diǎn)(node)有一個(gè)或者多個(gè)輸入和零個(gè)或者多個(gè)輸出蛹含,表示一種操作(operation)的實(shí)例化毅厚。流過圖中正常的邊(輸出到輸入)的值都是張量(tensor),任意維度的數(shù)組其中基礎(chǔ)元素類型是指定的或者在圖的構(gòu)造過程中自動(dòng)推斷出來(lái)的浦箱。特別的邊吸耿,我們稱之為控制依賴(control dependencies)祠锣,同樣也存在在圖中:這類邊上沒有數(shù)據(jù)流過,但是他們表示源節(jié)點(diǎn)必須在目標(biāo)節(jié)點(diǎn)的控制依賴開始執(zhí)行前完成運(yùn)行咽安。?由于我們的模型包括可變狀態(tài)伴网,控制依賴可以被直接用來(lái)確保 happens-before 關(guān)系。我們的實(shí)現(xiàn)同樣會(huì)插入控制依賴來(lái)確保獨(dú)立操作之間的順序妆棒,比如說(shuō)作為控制內(nèi)存使用最高峰值的方式澡腾。

In computer science, the happened-before relation (denoted:

) is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow). This involves ordering events based on the potential causal relationship of pairs of events in a concurrent system, especially asynchronous distributed systems. It was formulated by Leslie Lamport.[1]
In Java specifically, a happens-before relationship is a guarantee that memory written to by statement A is visible to statement B, that is, that statement A completes its write before statement B starts its read.[1]

操作和核(kernel)

一個(gè)操作有一個(gè)名字。它表示一個(gè)抽象的計(jì)算(比如說(shuō)糕珊,“矩陣相乘”或者“相加”)动分。一個(gè)操作可以有屬性(attribute),所有的屬性必須提供或者在圖構(gòu)造的過程中推斷出以實(shí)例化一個(gè)節(jié)點(diǎn)來(lái)執(zhí)行操作红选。屬性通常的使用方式是讓操作在不同的張量元素類型上多態(tài)(例如澜公,兩個(gè) float 類型的張量和兩個(gè) int32 類型的張量)。(kernel)是一種操作的特別實(shí)現(xiàn)喇肋,可以運(yùn)行在一個(gè)特定類型的設(shè)備上(如 CPU 或者 GPU)坟乾。TensorFlow 的 binary 定義了可以通過注冊(cè)(registration)機(jī)制實(shí)現(xiàn)的操作和核的集合上,這個(gè)集合可以通過連接額外的操作/核的定義/注冊(cè)苟蹈。表 1 展示了內(nèi)置于 TensorFlow 核心庫(kù)的一些操作類型糊渊。

表 1:TensorFlow 的操作類型

會(huì)話(session)

客戶端通過創(chuàng)建會(huì)話(session)和 TensorFlow 系統(tǒng)進(jìn)行交互。為了創(chuàng)建一個(gè)計(jì)算圖慧脱,會(huì)話接口支持外部(external)方法來(lái)提升當(dāng)前由包含額外節(jié)點(diǎn)和邊的會(huì)話的圖(當(dāng)會(huì)話創(chuàng)建時(shí)初始的圖是空的)渺绒。另一個(gè)由會(huì)話接口提供的主要的操作就是 Run,以需要計(jì)算的輸出名稱和替換某些輸出節(jié)點(diǎn)的張量的操作集合作為其參數(shù)輸入菱鸥。通過控制 Run 的參數(shù)宗兼,TensorFlow 的實(shí)現(xiàn)可以計(jì)算所有節(jié)點(diǎn)的必須執(zhí)行傳遞閉包來(lái)計(jì)算需要的輸出,然后安排執(zhí)行合適節(jié)點(diǎn)來(lái)保證他們的依賴關(guān)系(在3.1小節(jié)詳細(xì)講解)氮采。大多數(shù) TensorFlow 的使用都是針對(duì)一個(gè)圖啟動(dòng)一個(gè)會(huì)話殷绍,然后執(zhí)行整個(gè)圖或者通過 Run 調(diào)用來(lái)執(zhí)行分離的子圖數(shù)千或者數(shù)百萬(wàn)次。

變量(variable)

在大多數(shù)計(jì)算中鹊漠,圖都是執(zhí)行多次的主到。大多數(shù)的張量在一次執(zhí)行后不會(huì)存活。然而躯概,變量(variable)是一種特別的操作可以返回一個(gè)在圖執(zhí)行若干次過程中存活的持久化的可變張量的句柄登钥。這個(gè)句柄可以傳遞給一系列特定的操作,例如 Assign 和 AssignAdd(等同于 +=)就可以改變其引用的張量了娶靡。對(duì)應(yīng) TensorFlow 在機(jī)器學(xué)習(xí)中的應(yīng)用牧牢,模型的參數(shù)典型地就存放在變量引用的張量中,并作為模型訓(xùn)練圖的 Run 的一部分進(jìn)行更新。

3 實(shí)現(xiàn)

TensorFlow 系統(tǒng)的主要部分就是客戶端塔鳍,它使用了會(huì)話接口來(lái)和 master 及一個(gè)或者多個(gè)的 worker processes 進(jìn)行通信伯铣,每個(gè) worker process 負(fù)責(zé)對(duì)一個(gè)或者多個(gè)計(jì)算設(shè)備(CPU 核或者 GPU card)的任意訪問和在這些設(shè)備上進(jìn)行圖節(jié)點(diǎn)的計(jì)算按照 master 的要求執(zhí)行。我們有本地和分布式實(shí)現(xiàn)的 TensorFlow 接口轮纫。本地實(shí)現(xiàn)通常是客戶端腔寡、master 和 worker 都是在同一臺(tái)機(jī)器上在一個(gè)單一的操作系統(tǒng)進(jìn)程(可能包括多個(gè)設(shè)備,比如說(shuō)裝了多個(gè) GPU card的設(shè)備)上運(yùn)行蜡感。分布式實(shí)現(xiàn)采用了本地實(shí)現(xiàn)的很多的代碼蹬蚁,但是擴(kuò)展了對(duì)客戶端、master 和 worker 可以在不同的機(jī)器的不同的進(jìn)程上運(yùn)行的場(chǎng)景支持郑兴。在我們的分布式環(huán)境中犀斋,這些不同的任務(wù)對(duì)應(yīng)于 cluster 調(diào)度系統(tǒng)分配在 job 中的容器中[51]。這兩種不同的模式在圖 3 中進(jìn)行的展示情连。本節(jié)剩下的部分討論了在兩種實(shí)現(xiàn)中遇到的問題叽粹,3.3 節(jié)討論了針對(duì)分布式實(shí)現(xiàn)的一些問題。

設(shè)備

設(shè)備是 TensorFlow 的計(jì)算核心却舀。每個(gè) worker 負(fù)責(zé)一個(gè)或者多個(gè)設(shè)備虫几,每個(gè)設(shè)備有一個(gè)設(shè)備類型和一個(gè)名字。設(shè)備名字由識(shí)別設(shè)備類型的部分挽拔,在 worker 中的設(shè)備索引辆脸,以及在分布式設(shè)定中,worker 的 job和任務(wù)(或者 localhost 當(dāng)設(shè)備是和進(jìn)程在同一機(jī)器時(shí))的標(biāo)志構(gòu)成螃诅。一些例子如/job:localhost/device:cpu:0 或者 /job:worker/task:17/device:gpu:3啡氢。我們已實(shí)現(xiàn)了 CPU 和 GPU 的設(shè)備接口而其他的設(shè)備類型也有了通過注冊(cè)機(jī)制完成的設(shè)備實(shí)現(xiàn)方式。每個(gè)設(shè)備對(duì)象負(fù)責(zé)管理分配和解除分配設(shè)備內(nèi)存术裸,對(duì)在 TensorFlow 實(shí)現(xiàn)中的更高層請(qǐng)求任意 kernel 的執(zhí)行調(diào)度管理倘是。

張量

實(shí)現(xiàn)中的張量是一種有類型的、多維度數(shù)組袭艺。我們支持若干張量元素類型搀崭,包含大小為從 8 bit 到 64 bit 的帶符號(hào)和無(wú)符號(hào)整型,IEEE 浮點(diǎn)數(shù)和雙精度類型猾编、復(fù)數(shù)類型和字符串類型(任意長(zhǎng)的字節(jié)數(shù)組)瘤睹。合適大小的后臺(tái)存儲(chǔ)通過一個(gè)分配器進(jìn)行管理,該分配器?由張量所處的設(shè)備確定答倡。張量的后端存儲(chǔ)緩存是引用計(jì)數(shù)的并在沒有引用存在時(shí)解除分配默蚌。

3.1 單設(shè)備執(zhí)行

首先考慮最簡(jiǎn)單的執(zhí)行場(chǎng)景:?jiǎn)我坏膚orker進(jìn)程運(yùn)行在單一的設(shè)備上。圖上的節(jié)點(diǎn)按照代表節(jié)點(diǎn)之間的順序執(zhí)行苇羡。特別地,我們會(huì)在每個(gè)節(jié)點(diǎn)上保持一個(gè)計(jì)數(shù)來(lái)記錄這個(gè)節(jié)點(diǎn)上還沒有執(zhí)行的依賴。一旦這個(gè)計(jì)數(shù)變?yōu)?0设江,節(jié)點(diǎn)就可以被調(diào)度使用锦茁,并會(huì)加入到待續(xù)的隊(duì)列中。待續(xù)隊(duì)列按照某個(gè)非指定的順序處理叉存,指派節(jié)點(diǎn)執(zhí)行的kernel 到設(shè)備對(duì)象上码俩。當(dāng)一個(gè)節(jié)點(diǎn)完成執(zhí)行,所有依賴這個(gè)完成的節(jié)點(diǎn)的節(jié)點(diǎn)的計(jì)數(shù)都會(huì)減少歼捏。

3.2 多設(shè)備執(zhí)行

一旦系統(tǒng)有了多個(gè)設(shè)備稿存,有兩個(gè)主要的復(fù)雜情形出現(xiàn):確定圖中每個(gè)節(jié)點(diǎn)的計(jì)算所處的設(shè)備,然后管理由上一步確定的置放決定產(chǎn)生的設(shè)備間的所需的數(shù)據(jù)通信瞳秽。后續(xù)部分討論這兩個(gè)問題瓣履。

3.2.1 節(jié)點(diǎn)的置放

給定計(jì)算圖,TensorFlow 實(shí)現(xiàn)的主要責(zé)任之一就是將計(jì)算映射到可用的設(shè)備集合上练俐。這個(gè)算法的簡(jiǎn)單版本下面給出袖迎。參見第 4.3 節(jié)有關(guān)該算法支持的擴(kuò)展。

該置放算法的輸入是一個(gè)代價(jià)模型腺晾,包括對(duì)每個(gè)圖節(jié)點(diǎn)的輸入和輸出張亮的規(guī)模的估計(jì)燕锥,和對(duì)每個(gè)節(jié)點(diǎn)在給于其輸入張量時(shí)的計(jì)算時(shí)間的。這個(gè)代價(jià)模型或者是基于關(guān)聯(lián)不同操作類型的啟發(fā)式規(guī)則的靜態(tài)估計(jì)悯蝉,或者基于實(shí)際的為更早的圖的執(zhí)行而做的置放決定集合衡量归形。

置放算法首先運(yùn)行模擬的圖的執(zhí)行過程。模擬按照下面描述進(jìn)行鼻由,對(duì)每個(gè)節(jié)點(diǎn)使用貪心策略選擇一個(gè)設(shè)備暇榴。節(jié)點(diǎn)到設(shè)備的置放過程也是用作真實(shí)執(zhí)行的置放。

置放算法從計(jì)算圖的源點(diǎn)開始嗡靡,在系統(tǒng)中的每個(gè)設(shè)備上模擬相應(yīng)的活動(dòng)跺撼。對(duì)每個(gè)在遍歷中抵達(dá)的節(jié)點(diǎn),可選 available 設(shè)備的集合會(huì)被考慮到(設(shè)備可能會(huì)由于其沒能提供實(shí)現(xiàn)了特定操作的kernel而不可選)讨彼。對(duì)那些擁有多個(gè)可選設(shè)備的節(jié)點(diǎn)歉井,置放算法使用一種貪心策略來(lái)檢查在每個(gè)可能誰(shuí)被上置放節(jié)點(diǎn)需要完成的時(shí)間的效果完成決策。這種啟發(fā)式規(guī)則考慮了根據(jù)代價(jià)模型在那種設(shè)備上估計(jì)的和衡量的執(zhí)行時(shí)間哈误,還有任何用來(lái)從其他設(shè)備傳輸輸入到該節(jié)點(diǎn)的通信的代價(jià)哩至。其中節(jié)點(diǎn)的操作完成最快的設(shè)備會(huì)被選作該操作的設(shè)備,置放決策然后會(huì)繼續(xù)針對(duì)圖中其他的節(jié)點(diǎn)進(jìn)行處理蜜自,包含那些已經(jīng)做好模擬執(zhí)行的下游節(jié)點(diǎn)菩貌。第 4.3 節(jié)描述了一些擴(kuò)展,讓用戶可以提供提示和部分限制來(lái)指導(dǎo)置放算法重荠。這個(gè)算法現(xiàn)在還在持續(xù)開發(fā)的過程中箭阶。

3.2.2 交叉設(shè)備通信

一旦節(jié)點(diǎn)置放已經(jīng)計(jì)算好,圖就被劃分成子圖的集合,每個(gè)子圖對(duì)應(yīng)于一個(gè)設(shè)備仇参。從 xy 任何交叉設(shè)備的邊都會(huì)被移除并用一條從 x 到一個(gè) x 的子圖中新的 Send 節(jié)點(diǎn)的邊和從在 y 子圖中對(duì)應(yīng)的 Receive 節(jié)點(diǎn)到 y 的邊代替嘹叫。參見圖 4 中所進(jìn)行的變換。

圖 4

在運(yùn)行時(shí)刻诈乒,Send 和 Receive 節(jié)點(diǎn)合作進(jìn)行跨設(shè)備的數(shù)據(jù)交換罩扇。這使得我們可以隔離所有在 Send 和 Receive 內(nèi)部實(shí)現(xiàn)的通信,這樣簡(jiǎn)化了運(yùn)行時(shí)刻剩下的部分工作怕磨。
當(dāng)我們插入 Send 和 Receive 節(jié)點(diǎn)時(shí)喂饥,我們將在特定設(shè)備上的特定張量的所有使用者進(jìn)行合并規(guī)整來(lái)使用單個(gè) Receive 節(jié)點(diǎn),而不是對(duì)特定設(shè)備上的每個(gè)下游使用者都給一個(gè) Receive 節(jié)點(diǎn)肠鲫。這確保了需要使用的張量數(shù)據(jù)僅僅會(huì)從源設(shè)備到目的設(shè)備傳輸一次员帮,而在目的設(shè)備上的張量?jī)?nèi)存也只會(huì)分配一次(而非多次,參看圖 4 的節(jié)點(diǎn) bc)滩届。

通過這種方式處理通信集侯,我們也允許了不同設(shè)備上的圖中的個(gè)別節(jié)點(diǎn)調(diào)度可以被去中心化到 workers 上:Send 和 Receive 節(jié)點(diǎn)傳達(dá)了在不同的 worker 和 設(shè)備間必要的同步信息,master 僅僅需要對(duì)每個(gè)圖的執(zhí)行給出一個(gè) Run 請(qǐng)求給那些包含圖中任意節(jié)點(diǎn)的 worker帜消,而不是會(huì)對(duì)所有節(jié)點(diǎn)或者每個(gè)跨設(shè)備通信都進(jìn)行調(diào)度棠枉。這也讓系統(tǒng)更加可擴(kuò)展,并允許比通過 master 來(lái)強(qiáng)制進(jìn)行所有的調(diào)度更加精確的節(jié)點(diǎn)執(zhí)行泡挺。

3.3 分布式執(zhí)行

計(jì)算圖的分布式執(zhí)行非常類似于多設(shè)備執(zhí)行辈讶。在設(shè)備置放后,子圖會(huì)針對(duì)每個(gè)設(shè)備創(chuàng)建娄猫。用于 worker 進(jìn)程之間的通信的 Send/Receive 節(jié)點(diǎn)對(duì)使用了諸如 TCP 或者 RDMA 這樣的遠(yuǎn)程通信機(jī)制進(jìn)行跨機(jī)器的數(shù)據(jù)遷移贱除。

容錯(cuò)

分布式執(zhí)行中的錯(cuò)誤可以在很多地方進(jìn)行檢測(cè)。最主要的有 (a) 在 Send 和 Receive 節(jié)點(diǎn)對(duì)之間的通信錯(cuò)誤媳溺,(b) 從 master 進(jìn)程到每個(gè) worker 進(jìn)程的周期性的健康狀態(tài)檢測(cè)月幌。

如果發(fā)現(xiàn)了錯(cuò)誤,整個(gè)圖的執(zhí)行就會(huì)終止悬蔽,并從頭開始扯躺。但是回想之前變量節(jié)點(diǎn)對(duì)應(yīng)于那些在執(zhí)行過程中記憶持有(persist)的張量。我們支持在重啟過程中的一致的檢查點(diǎn)和狀態(tài)恢復(fù)蝎困。特別是录语,每個(gè)變量節(jié)點(diǎn)連接在一個(gè) Save 節(jié)點(diǎn)上。這些 Save 節(jié)點(diǎn)周期性地執(zhí)行禾乘,比如說(shuō)每 N 次迭代澎埠,或者每隔 N 秒。他們執(zhí)行的時(shí)候始藕,變量的內(nèi)容被寫到持久化的存儲(chǔ)中蒲稳,比如說(shuō)氮趋,一個(gè)分布式的文件系統(tǒng)早抠。類似地辜梳,每個(gè)變量連接在一個(gè) Restore 節(jié)點(diǎn)上,只會(huì)在一次重啟后的第一個(gè)迭代中啟用苇本。在 4.2 節(jié)有某些節(jié)點(diǎn)僅能夠在某些圖的執(zhí)行中啟用的細(xì)節(jié)决记。

4 擴(kuò)展

本節(jié)我們描述在第 2 節(jié)給出的編程模型中幾個(gè)更加高級(jí)的特性。

4.1 梯度計(jì)算

很多優(yōu)化算法倍踪,包括常用的機(jī)器學(xué)習(xí)訓(xùn)練算法系宫,如隨機(jī)梯度下降 [45],計(jì)算代價(jià)函數(shù)關(guān)于一個(gè)輸入集合的梯度建车。由于該算法廣泛的應(yīng)用需求扩借,TensorFlow 已經(jīng)內(nèi)置了對(duì)自動(dòng)梯度計(jì)算的支持。如果張量 C 通過一個(gè)復(fù)雜的子圖操作依賴于張量 {X_k} 集合缤至,那么就有一個(gè)內(nèi)置的函數(shù)可以返回張量 {dC/dX_k}潮罪。梯度張量如同其他張量一樣通過擴(kuò)展 TensorFlow 圖使用下面的流程進(jìn)行計(jì)算的。

圖 5

當(dāng) TensorFlow 需要計(jì)算一個(gè)張量 C 關(guān)于某個(gè)張量 I 時(shí)领斥,首先找出計(jì)算圖中從 IC 的路徑嫉到。然后從 C 回溯到 I,而對(duì)在回溯路徑中的每個(gè)操作都會(huì)添加一個(gè)節(jié)點(diǎn)到 TensorFlow 的圖上月洛,包含回溯路徑上使用鏈?zhǔn)椒▌t的偏導(dǎo)數(shù)何恶。新加的節(jié)點(diǎn)計(jì)算前向路徑中相對(duì)應(yīng)的操作的梯度函數(shù)。梯度函數(shù)可以是任何的操作嚼黔。這個(gè)函數(shù)不但可以用在反向路徑中計(jì)算出的偏導(dǎo)數(shù)作為輸入细层,同樣也能選擇性地用前向操作的輸入輸出作為輸入。圖 5 展示了圖 2 中例子的代價(jià)函數(shù)的梯度唬涧。Grey arrows show potential inputs to gradient functions that are not used for the particular operations shown. 圖 1 所對(duì)應(yīng)的是計(jì)算這些梯度:

[db, dW, dx] = tf.gradients(C, [b, W, x])

通常操作可能會(huì)有多個(gè)輸出疫赎,C 可能僅僅會(huì)依賴于其中一部分。例如碎节,如果操作 O 有兩個(gè)輸出 y_1y_2捧搞,C 僅僅依賴于 y_2,那么 O 的梯度函數(shù)的第一個(gè)輸入就可以設(shè)置為 0 因?yàn)?dC/dy_1 = 0钓株。

自動(dòng)梯度計(jì)算讓優(yōu)化尤其是內(nèi)存耗用變得復(fù)雜实牡。在執(zhí)行前向計(jì)算子圖時(shí),那些顯式地由用戶創(chuàng)建的操作轴合,可能的啟發(fā)式想法就是通過觀察圖被構(gòu)建的次序來(lái)確定哪個(gè)節(jié)點(diǎn)執(zhí)行下一步创坞。

這通常指的是臨時(shí)輸出會(huì)在創(chuàng)建后不久就是用到,所以他們的內(nèi)存可以很快重新用到受葛。當(dāng)這個(gè)啟發(fā)式想法不適用時(shí)题涨,用戶可能會(huì)改變圖構(gòu)建的次序偎谁,或者增加在第 5 節(jié)將會(huì)介紹的控制依賴。當(dāng)梯度節(jié)點(diǎn)自動(dòng)加入到圖中時(shí)纲堵,用戶控制就會(huì)變?nèi)跹灿辏瑔l(fā)式想法就失效了。特別地席函,因?yàn)樘荻饶孓D(zhuǎn)了前向計(jì)算的順序铐望,在圖早期用到的張量會(huì)在梯度計(jì)算的結(jié)尾時(shí)重新被頻繁用到。這樣的張量會(huì)消耗很多的 GPU 內(nèi)存茂附,也就不必要地限制了計(jì)算的規(guī)模正蛙。我們正積極地提升內(nèi)存關(guān)聯(lián)的效果以更好地處理這個(gè)問題。是用更加精密的啟發(fā)式想法來(lái)確定圖執(zhí)行的次序营曼,重計(jì)算張量而不是存儲(chǔ)在內(nèi)存乒验,將長(zhǎng)效的張量從 GPU 內(nèi)存中移到 CPU 內(nèi)存中等等都是可以嘗試的思路。

4.2 部分執(zhí)行

常常有 client 希望執(zhí)行整個(gè)圖的子圖蒂阱。為了支持這一點(diǎn)锻全,只要 client 在 Session 中構(gòu)建出一個(gè)計(jì)算圖,我們 Run 的方法運(yùn)行他們執(zhí)行整個(gè)計(jì)算圖的任意的子圖录煤,并將任意的數(shù)據(jù)注入到圖中的任何邊上鳄厌,以及獲取流經(jīng)任何邊上的數(shù)據(jù)。

圖中每個(gè)節(jié)點(diǎn)都有一個(gè)名字辐赞,一個(gè)節(jié)點(diǎn)的每個(gè)輸出都通過源節(jié)點(diǎn)名和節(jié)點(diǎn)輸出端口確定部翘,從 0 開始計(jì)數(shù)(例如,“bar:0” 表示 “bar” 節(jié)點(diǎn)的第一個(gè)輸出响委,而 “bar:1” 則是第二個(gè)輸出)新思。

Run 調(diào)用的兩個(gè)參數(shù)可以幫助定義準(zhǔn)確的將要執(zhí)行的子圖。第一赘风,Run 調(diào)用接受輸入夹囚,一個(gè)可選的映射 name:port 名來(lái)填充張量值。第二邀窃,Run 調(diào)用接受 output_names荸哟,輸出 name[:port] 列表指定了需要執(zhí)行的節(jié)點(diǎn),并且瞬捕,如果端口出現(xiàn)在名字中鞍历,那么對(duì)那個(gè)節(jié)點(diǎn)的特定輸出張量值就應(yīng)該返回給 client 如果 Run 調(diào)用成功完成。

Paste_Image.png

計(jì)算圖是基于輸入輸出的值進(jìn)行變換的肪虎。每個(gè)在輸入中指定的 node:port 使用 feed 節(jié)點(diǎn)替換劣砍,這個(gè)會(huì)選出從特定初始化的用來(lái)給 Run 調(diào)用的 Rensezvous 對(duì)象的入口選擇出給定的輸入向量。類似地扇救,每個(gè)輸出名字關(guān)聯(lián)在一個(gè)特定的 fetch 節(jié)點(diǎn)上刑枝,可供輸出張量的存儲(chǔ)香嗓,并在 Run 調(diào)用完成時(shí)返回給客戶端。最后装畅,一旦圖已經(jīng)被這些特定的 feefetch 節(jié)點(diǎn)重寫靠娱,將要執(zhí)行的節(jié)點(diǎn)集合可以被從每個(gè)被輸出命名的節(jié)點(diǎn)出發(fā),然后使用圖的依賴關(guān)系來(lái)確定必須在重寫圖中執(zhí)行產(chǎn)生輸出的整個(gè)節(jié)點(diǎn)的集合中進(jìn)行反向傳播掠兄。圖 6 展示了左邊的原始圖像云,以及變換的圖,其中 Run 被激活輸入是 蚂夕 輸出是 {f:0}苫费。因?yàn)槲覀儍H僅需要計(jì)算節(jié)點(diǎn) f 的輸出,我們不需要執(zhí)行節(jié)點(diǎn) de双抽,因?yàn)樗麄儧]有對(duì) f 做出貢獻(xiàn)。

4.3 設(shè)備限制

TensorFlow 客戶端可以通過為一個(gè)節(jié)點(diǎn)哪些設(shè)備可以在其上執(zhí)行來(lái)提供部分的限制來(lái)控制節(jié)點(diǎn)在設(shè)備中的放置闲礼。例如牍汹,“僅僅放置這個(gè)節(jié)點(diǎn)在類型為 GPU 的設(shè)備上” 或者 “這個(gè)節(jié)點(diǎn)可以被放置在任何在 /job:worker/task:17 中的設(shè)備上”,或者 “共享這個(gè)節(jié)點(diǎn)和 variable13 節(jié)點(diǎn)”柬泽。按照這些限制慎菲,放置算法就可以負(fù)責(zé)選擇節(jié)點(diǎn)的分配來(lái)提供快速執(zhí)行,并且滿足不同的設(shè)備本身的限制锨并,例如內(nèi)存的總量的限制來(lái)執(zhí)行子圖的計(jì)算露该。

支持這樣的限制需要對(duì) 3.2.1 節(jié)介紹的放置算法進(jìn)行調(diào)整。我們首先計(jì)算每個(gè)節(jié)點(diǎn)的可行設(shè)備集合第煮,然后使用 union-find 算法來(lái)計(jì)算圖中必須放在一起的圖的組成部分解幼。對(duì)每個(gè)這樣的組成部分,我們計(jì)算可行設(shè)備集合的交集包警。計(jì)算出的每個(gè)節(jié)點(diǎn)可行設(shè)備和放置算法的模擬器很容易匹配撵摆。

4.4 控制流

盡管沒有任何顯式的控制流數(shù)據(jù)流圖非常強(qiáng)大,但是我們發(fā)現(xiàn)了一些例子中支持條件和循環(huán)可以得到更加簡(jiǎn)潔而高效的機(jī)器學(xué)習(xí)算法的表示害晦。

在 Arvind [3] 中描述的數(shù)據(jù)流機(jī)器觀點(diǎn)特铝,我們引入了一個(gè)小控制流操作的集合進(jìn)入 TensorFlow 并且推廣 TensorFlow 使之能夠處理循環(huán)的數(shù)據(jù)流圖。SwitchMerge 操作符可以讓我們基于布爾值的張量來(lái)跳過整個(gè)子圖的執(zhí)行壹瘟。Enter Leave NextIteration 操作符可以進(jìn)行迭代鲫剿。高級(jí)程序構(gòu)造如 if-conditional 和 while-loop 可以很容易用這些控制流操作編譯成數(shù)據(jù)流圖。

TensorFlow 運(yùn)行時(shí)刻實(shí)現(xiàn)了一個(gè) tags 和 frames 概念稻轨,這與 MIT Tagged Token Machine 類似灵莲。每次迭代都使用了唯一一個(gè) tag,這個(gè)執(zhí)行的狀態(tài)通過 frame 表示的澄者。只要被使用笆呆,輸入就可以進(jìn)入一次迭代请琳;因此,多次迭代可以被并發(fā)地執(zhí)行赠幕。

TensorFlow 使用分布式協(xié)同機(jī)制來(lái)使用控制流進(jìn)行圖的執(zhí)行俄精。一般來(lái)說(shuō),循環(huán)可以包含被分配到多個(gè)不同的設(shè)備上的節(jié)點(diǎn)榕堰。因此竖慧,管理循環(huán)的狀態(tài)成為了一個(gè)分布式終止檢測(cè)的問題。TensorFlow 的解決方案基于圖的重寫逆屡。在圖的劃分(partitioning)過程中圾旨,我們自動(dòng)地增加控制節(jié)點(diǎn)到每個(gè)劃分(partition)上。這些節(jié)點(diǎn)實(shí)現(xiàn)了一個(gè)小的狀態(tài)機(jī)魏蔗,來(lái)管理每次迭代的開始和終止砍的,確定整個(gè)循環(huán)的終止。

如上所示莺治,我們通常通過隨機(jī)梯度下降來(lái)訓(xùn)練機(jī)器學(xué)習(xí)模型廓鞠,將梯度計(jì)算表示成數(shù)據(jù)流圖的一部分。在模型包含控制流操作時(shí)谣旁,我們必須在對(duì)應(yīng)的梯度計(jì)算中處理這些操作床佳。例如,使用 if-conditional 來(lái)對(duì)模型梯度計(jì)算時(shí)榄审,需要知道哪個(gè)分值會(huì)被選擇砌们,然后應(yīng)用梯度邏輯到這個(gè)分支上。類似地搁进,使用 while-loop 來(lái)對(duì)模型梯度計(jì)算時(shí)浪感,需要知道多少次迭代,同樣還依賴在這些迭代中出現(xiàn)的中間值拷获±撼牛基本技術(shù)就是重寫這些圖來(lái)記住需要計(jì)算梯度計(jì)算的值。我們這里省略掉細(xì)節(jié)介紹匆瓜。

4.5 輸入操作

盡管輸入數(shù)據(jù)可以被通過 feed 節(jié)點(diǎn)提供給計(jì)算赢笨,另一個(gè)用來(lái)訓(xùn)練大規(guī)模機(jī)器學(xué)習(xí)模型通常的機(jī)制是在圖中采用特定的輸入操作節(jié)點(diǎn),這些節(jié)點(diǎn)一般來(lái)說(shuō)會(huì)通過文件名配置驮吱,并產(chǎn)生一個(gè)包含一個(gè)或者多個(gè)樣本的張量來(lái)自在每次執(zhí)行時(shí)的那些文件集合中存放的數(shù)據(jù)茧妒。這使得數(shù)據(jù)可以被直接從底層存儲(chǔ)系統(tǒng)讀出到將要執(zhí)行接下來(lái)處理的內(nèi)存中。在配置中左冬,從 worker 進(jìn)程分隔開的客戶端進(jìn)程桐筏,如果數(shù)據(jù)給定,一般會(huì)需要一個(gè)額外的網(wǎng)絡(luò) hop(從存儲(chǔ)系統(tǒng)到客戶端然后從客戶端到 worker vs. 使用一個(gè)輸入節(jié)點(diǎn)時(shí)直接從存儲(chǔ)系統(tǒng)到 worker)拇砰。

4.6 隊(duì)列

隊(duì)列是一個(gè)加入到 TensorFlow 中的有用的特性梅忌。他們?cè)试S圖的不同部分來(lái)異步地執(zhí)行狰腌,可能按照不同的節(jié)奏,來(lái)通過 Enqueue 和 Dequeue 操作來(lái)處理數(shù)據(jù)牧氮。在隊(duì)列中空間尚存時(shí)琼腔,可以進(jìn)行 Enqueue 操作;在指定的最小數(shù)量的元素可以取出時(shí)踱葛,可以進(jìn)行 Dequeue 操作丹莲。隊(duì)列的一個(gè)用途就是允許輸入數(shù)據(jù)可以從磁盤文件中預(yù)先取出,這樣可以同時(shí)進(jìn)行前一批的數(shù)據(jù)的處理尸诽。同樣還能用來(lái)進(jìn)行其他類型的歸類甥材,包括聚合很多梯度來(lái)計(jì)算某種更加復(fù)雜的梯度組合,或者來(lái)組織不同的輸入語(yǔ)句到遞歸語(yǔ)言模型到語(yǔ)句的近似同樣長(zhǎng)度的 bins 中性含,這樣處理得更有效率洲赵。

在一般的 FIFO 隊(duì)列上,我們還實(shí)現(xiàn)了一個(gè) shuffling 隊(duì)列商蕴,可以對(duì)內(nèi)存內(nèi)的緩沖區(qū)內(nèi)的元素進(jìn)行隨機(jī)洗牌板鬓。洗牌功能在機(jī)器學(xué)習(xí)算法中比較有用,常常需要對(duì)樣本進(jìn)行隨機(jī)化究恤。

4.7 容器

容器 是用來(lái)管理長(zhǎng)期存在的可變狀態(tài)的機(jī)制。變量 Variable 反向存放在一個(gè)容器內(nèi)后德。默認(rèn)的容器是直到進(jìn)程終止時(shí)都是存活的部宿,但是我們也允許其他的容器。容器可以通過清除它的整個(gè)內(nèi)容進(jìn)行重置瓢湃。使用容器理张,就可以分享狀態(tài)在完全不相交的不同會(huì)話中的計(jì)算圖中。

5 優(yōu)化

本節(jié)绵患,我們給出一些在 TensorFlow 實(shí)現(xiàn)中的優(yōu)化雾叭,這些優(yōu)化對(duì)系統(tǒng)性能和資源利用率起到了提升作用。

5.1 共同子表達(dá)式消除

因?yàn)橛?jì)算圖的構(gòu)造通常是由很多不同層的抽象完成的落蝙,計(jì)算圖一般都會(huì)包含冗余的計(jì)算過程的多個(gè)副本织狐。為了解決這個(gè)問題,我們已經(jīng)實(shí)現(xiàn)了一個(gè)共同子表達(dá)式過程筏勒,類似于 Click[12] 中給出的算法移迫,運(yùn)行在計(jì)算圖上,并將操作的多重復(fù)制管行,用這些節(jié)點(diǎn)中的一個(gè)單獨(dú)節(jié)點(diǎn)代替厨埋,并將圖的邊重定向以滿足這個(gè)歸一化。

5.2 控制數(shù)據(jù)通信和內(nèi)存分配

5.3 異步 kernel

5.4 用于 kernel 實(shí)現(xiàn)的優(yōu)化庫(kù)

5.5 ?有損壓縮

6 狀態(tài)和經(jīng)驗(yàn)

7 常用編程規(guī)范

8 性能

9 工具

9.1 TensorBoard: 圖結(jié)構(gòu)和總結(jié)統(tǒng)計(jì)可視化

9.2 性能追蹤

10 未來(lái)工作

11 相關(guān)工作

12 結(jié)論

參考文獻(xiàn)


完整內(nèi)容請(qǐng)大家關(guān)注我們 UAI 的創(chuàng)業(yè)項(xiàng)目:

  • 微信公眾號(hào):UAI 人工智能
UAI 人工智能
  • 微信號(hào):FEIGlobal
    如要咨詢相關(guān)項(xiàng)目可以聯(lián)系該微信號(hào)捐顷。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荡陷,一起剝皮案震驚了整個(gè)濱河市雨效,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌废赞,老刑警劉巖徽龟,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛹头,居然都是意外死亡顿肺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門渣蜗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)屠尊,“玉大人,你說(shuō)我怎么就攤上這事耕拷∷侠ィ” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵骚烧,是天一觀的道長(zhǎng)浸赫。 經(jīng)常有香客問我,道長(zhǎng)赃绊,這世上最難降的妖魔是什么既峡? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮碧查,結(jié)果婚禮上运敢,老公的妹妹穿的比我還像新娘。我一直安慰自己忠售,他們只是感情好传惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稻扬,像睡著了一般卦方。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泰佳,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天盼砍,我揣著相機(jī)與錄音,去河邊找鬼逝她。 笑死衬廷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汽绢。 我是一名探鬼主播吗跋,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了跌宛?” 一聲冷哼從身側(cè)響起酗宋,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疆拘,沒想到半個(gè)月后蜕猫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哎迄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年回右,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漱挚。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翔烁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旨涝,到底是詐尸還是另有隱情蹬屹,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布白华,位于F島的核電站慨默,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弧腥。R本人自食惡果不足惜厦取,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望管搪。 院中可真熱鬧蒜胖,春花似錦、人聲如沸抛蚤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岁经。三九已至,卻和暖如春蛇券,著一層夾襖步出監(jiān)牢的瞬間缀壤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工纠亚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塘慕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓蒂胞,卻偏偏與公主長(zhǎng)得像图呢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容