在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

XLA和PyTorch的鏈接代碼示例

jf_pmFSk4VX ? 來源:GiantPandaCV ? 2023-11-17 10:54 ? 次閱讀

XLA和PyTorch的鏈接

前言

XLA (Accelerated Linear Algebra)是一個開源的機器學(xué)習(xí)編譯器,對PyTorch、Tensorflow、JAX等多個深度學(xué)習(xí)框架都有支持。最初XLA實際上是跟Tensorflow深度結(jié)合的,很好地服務(wù)了Tensorflow和TPU,而與XLA的結(jié)合主要依賴于社區(qū)的支持,即torch-xla。

torch-xla在支持XLA編譯的基礎(chǔ)上,較大限度地保持了PyTorch的易用性,貼一個官方的DDP訓(xùn)練的例子:

importtorch.distributedasdist
-importtorch.multiprocessingasmp
+importtorch_xla.core.xla_modelasxm
+importtorch_xla.distributed.parallel_loaderaspl
+importtorch_xla.distributed.xla_multiprocessingasxmp
+importtorch_xla.distributed.xla_backend

def_mp_fn(rank,world_size):
...

-os.environ['MASTER_ADDR']='localhost'
-os.environ['MASTER_PORT']='12355'
-dist.init_process_group("gloo",rank=rank,world_size=world_size)
+#RankandworldsizeareinferredfromtheXLAdeviceruntime
+dist.init_process_group("xla",init_method='xla://')
+
+model.to(xm.xla_device())
+#`gradient_as_bucket_view=True`requiredforXLA
+ddp_model=DDP(model,gradient_as_bucket_view=True)

-model=model.to(rank)
-ddp_model=DDP(model,device_ids=[rank])
+xla_train_loader=pl.MpDeviceLoader(train_loader,xm.xla_device())

-forinputs,labelsintrain_loader:
+forinputs,labelsinxla_train_loader:
optimizer.zero_grad()
outputs=ddp_model(inputs)
loss=loss_fn(outputs,labels)
loss.backward()
optimizer.step()

if__name__=='__main__':
-mp.spawn(_mp_fn,args=(),nprocs=world_size)
+xmp.spawn(_mp_fn,args=())

將一段PyTorch代碼改寫為torch-xla代碼,主要就是三個方面:

將模型和數(shù)據(jù)放到xla device上

適當?shù)臅r候調(diào)用xm.mark_step

某些組件該用pytorchx-xla提供的,比如amp和spawn

其中第二條并沒有在上面的代碼中體現(xiàn),原因是為了讓用戶少改代碼,torch-xla將mark_step封裝到了dataloader中,實際上不考慮DDP的完整訓(xùn)練的過程可以簡寫如下:

device=xm.xla_device()
model=model.to(device)
fordata,labelinenumerate(dataloader):
data,label=data.to(device),label.to(device)
output=model(data)
loss=func(output,label)
loss.backward()
optimizer.step()
xm.mark_step()

xm.mark_step的作用就是"告訴"框架:現(xiàn)在對圖的定義告一段落了,可以編譯并執(zhí)行計算了。既然如此,那么mark_step之前的內(nèi)容是做了什么呢?因為要在mark_step之后才編譯并計算,那么前面肯定不能執(zhí)行實際的運算。這就引出了Trace和LazyTensor的概念。

其實到了這里,如果對tensorflow或者torch.fx等比較熟悉,就已經(jīng)很容易理解了,在mark_step之前,torch-xla將torch Tensor換成了LazyTensor,進而將原本是PyTorch中eager computation的過程替換成了trace的過程,最后生成一張計算圖來優(yōu)化和執(zhí)行。簡而言之這個過程是PyTorch Tensor -> XLATensor -> HLO IR,其中HLO就是XLA所使用的IR。在每次調(diào)用到torch op的時候,會調(diào)用一次GetIrValue,這時候就意味著一個節(jié)點被寫入了圖中。更具體的信息可以參考XLA Tensor Deep Dive這部分文檔。需要注意的是,trace這個過程是獨立于mark_step的,即便你的每個循環(huán)都不寫mark_step,這個循環(huán)也可以一直持續(xù)下去,只不過在這種情況下,永遠都不會發(fā)生圖的編譯和執(zhí)行,除非在某一步trace的時候,發(fā)現(xiàn)圖的大小已經(jīng)超出了pytorch-xla允許的上限。

PyTorch與torch-xla的橋接

知曉了Trace過程之后,就會好奇一個問題:當用戶執(zhí)行一個PyTorch函數(shù)調(diào)用的時候,torch-xla怎么將這個函數(shù)記錄下來的?

最容易想到的答案是“torch-xla作為PyTorch的一個編譯選項,打開的時候就會使得二者建立起映射關(guān)系”,但很可惜,這個答案是錯誤的,仔細看PyTorch的CMake文件以及torch-xla的編譯方式就會明白,torch-xla是幾乎單向依賴于PyTorch的(為什么不是全部后面會講)。既然PyTorch本身在編譯期間并不知道torch-xla的存在,那么當用戶使用一個xla device上的Tensor作為一個torch function的輸入的時候,又經(jīng)歷了怎樣一個過程調(diào)用到pytorch-xla中的東西呢?

從XLATensor開始的溯源

盡管我們現(xiàn)在并不知道怎么調(diào)用到torch-xla中的,但我們知道PyTorch Tensor一定要轉(zhuǎn)換成XLATensor(參考tensor.h),那么我們只需要在關(guān)鍵的轉(zhuǎn)換之處打印出調(diào)用堆棧,自然就可以找到調(diào)用方,這樣雖然不能保證找到PyTorch中的位置,但是能夠找到torch-xla中最上層的調(diào)用。注意到XLATensor只有下面這一個創(chuàng)建函數(shù)接受at::Tensor作為輸入,因此就在這里面打印調(diào)用棧。

XLATensorXLATensor::Create(constat::Tensor&tensor,constDevice&device)

測試的用例很簡單,我們讓兩個xla device上的Tensor相乘:

importtorch_xla.core.xla_modelasxm
importtorch

device=xm.xla_device()
a=torch.normal(0,1,(2,3)).to(device)
b=torch.normal(0,1,(2,3)).to(device)

c=a*b

在上述位置插入堆棧打印代碼并重新編譯、安裝后運行用例,可以看到以下輸出(截取部分):

usr/local/lib/python3.8/dist-packages/_XLAC.cpython-38-x86_64-linux-gnu.so(_ZN9torch_xla15TensorToXlaDataERKN2at6TensorERKNS_6DeviceEb+0x64d)[0x7f086098b9ed]
/usr/local/lib/python3.8/dist-packages/_XLAC.cpython-38-x86_64-linux-gnu.so(_ZNK9torch_xla9XLATensor19GetIrValueForTensorERKN2at6TensorERKNS_6DeviceE+0xa5)[0x7f0860853955]
/usr/local/lib/python3.8/dist-packages/_XLAC.cpython-38-x86_64-linux-gnu.so(_ZNK9torch_xla9XLATensor10GetIrValueEv+0x19b)[0x7f0860853d5b]
/usr/local/lib/python3.8/dist-packages/_XLAC.cpython-38-x86_64-linux-gnu.so(_ZN9torch_xla9XLATensor3mulERKS0_S2_N3c108optionalINS3_10ScalarTypeEEE+0x3f)[0x7f086087631f]
/usr/local/lib/python3.8/dist-packages/_XLAC.cpython-38-x86_64-linux-gnu.so(_ZN9torch_xla18XLANativeFunctions3mulERKN2at6TensorES4_+0xc4)[0x7f08606d4da4]
/usr/local/lib/python3.8/dist-packages/_XLAC.cpython-38-x86_64-linux-gnu.so(+0x19d158)[0x7f08605f7158]
/usr/local/lib/python3.8/dist-packages/torch/lib/libtorch_cpu.so(_ZN2at4_ops10mul_Tensor10redispatchEN3c1014DispatchKeySetERKNS_6TensorES6_+0xc5)[0x7f0945c9d055]
/usr/local/lib/python3.8/dist-packages/torch/lib/libtorch_cpu.so(+0x2b8986c)[0x7f094705986c]
/usr/local/lib/python3.8/dist-packages/torch/lib/libtorch_cpu.so(+0x2b8a37b)[0x7f094705a37b]
/usr/local/lib/python3.8/dist-packages/torch/lib/libtorch_cpu.so(_ZN2at4_ops10mul_Tensor4callERKNS_6TensorES4_+0x157)[0x7f0945cee717]
/usr/local/lib/python3.8/dist-packages/torch/lib/libtorch_python.so(+0x3ee91f)[0x7f094e4b391f]
/usr/local/lib/python3.8/dist-packages/torch/lib/libtorch_python.so(+0x3eeafb)[0x7f094e4b3afb]
python()[0x5042f9]

明顯可以看到是從python的堆棧調(diào)用過來的,分析一下可以得知_ZN2at4_ops10mul_Tensor10redispatchEN3c1014DispatchKeySetERKNS_6TensorES6_+0xc5對應(yīng)的定義是at::DispatchKeySet, at::Tensor const&, at::Tensor const&)+0xc5

雖然這里意義仍有些不明,但我們已經(jīng)可以做出推測了:redistpatch函數(shù)是根據(jù)DispatchKeySet來決定將操作dispatch到某個backend上,xla的device信息就被包含在其中。而后面兩個輸入的const at::Tensor&就是乘法操作的兩個輸入。

根據(jù)上面的關(guān)鍵字redispatch來尋找,我們可以找到這樣一個文件gen.py,其中的codegen函數(shù)很多,但最顯眼的是下面的OperatorGen:

@dataclass(frozen=True)
classComputeOperators:
target:Union[
Literal[Target.DECLARATION],
Literal[Target.DEFINITION]
]

@method_with_native_function
def__call__(self,f:NativeFunction)->str:
sig=DispatcherSignature.from_schema(f.func)
name=f.func.name.unambiguous_name()
call_method_name='call'
redispatch_method_name='redispatch'

ifself.targetisTarget.DECLARATION:
returnf"""
structTORCH_API{name}{{
usingschema={sig.type()};
usingptr_schema=schema*;
//SeeNote[staticconstexprchar*membersforwindowsNVCC]
STATIC_CONSTEXPR_STR_INL_EXCEPT_WIN_CUDA(name,"aten::{f.func.name.name}")
STATIC_CONSTEXPR_STR_INL_EXCEPT_WIN_CUDA(overload_name,"{f.func.name.overload_name}")
STATIC_CONSTEXPR_STR_INL_EXCEPT_WIN_CUDA(schema_str,{cpp_string(str(f.func))})
static{sig.defn(name=call_method_name,is_redispatching_fn=False)};
static{sig.defn(name=redispatch_method_name,is_redispatching_fn=True)};
}};"""
elifself.targetisTarget.DEFINITION:
defns=f"""
STATIC_CONST_STR_OUT_OF_LINE_FOR_WIN_CUDA({name},name,"aten::{f.func.name.name}")
STATIC_CONST_STR_OUT_OF_LINE_FOR_WIN_CUDA({name},overload_name,"{f.func.name.overload_name}")
STATIC_CONST_STR_OUT_OF_LINE_FOR_WIN_CUDA({name},schema_str,{cpp_string(str(f.func))})

//aten::{f.func}
staticC10_NOINLINEc10::TypedOperatorHandle<{name}::schema>create_{name}_typed_handle(){{
returnc10::singleton()
.findSchemaOrThrow({name}::name,{name}::overload_name)
.typed<{name}::schema>();
}}
"""

foris_redispatching_fnin[False,True]:
ifis_redispatching_fn:
dispatcher_exprs_str=','.join(['dispatchKeySet']+[a.nameforainsig.arguments()])
dispatcher_call='redispatch'
method_name=f'{name}::{redispatch_method_name}'
else:
dispatcher_exprs_str=','.join([a.nameforainsig.arguments()])
dispatcher_call='call'
method_name=f'{name}::{call_method_name}'

defns+=f"""
//aten::{f.func}
{sig.defn(name=method_name,is_redispatching_fn=is_redispatching_fn)}{{
staticautoop=create_{name}_typed_handle();
returnop.{dispatcher_call}({dispatcher_exprs_str});
}}
"""
returndefns
else:
assert_never(self.target)

對于每個算子,PyTorch會(在編譯前)在這里生成許多類,這些類會有靜態(tài)成員call或者redispatch,其中redispatch負責(zé)分發(fā)具體的實現(xiàn)。這里的codegen比較繁瑣,這里就不再細講。

注冊PyTorch庫實現(xiàn)

即便我們找到了上面redispatch和codegen的線索,看起來仍然不足以解釋PyTorch到torch-xla的橋接,因為PyTorch和torch-xla兩個庫之間的調(diào)用,必須要有符號的映射才可以,而不是一些函數(shù)形式上的相同。PyTorch中是有Dispatcher機制的,這個機制很常見于很多框架,比如oneflow也是有一套類似的Dispatcher機制。這套機制最大的好處就是在盡可能減少侵入式修改的前提下保證了較高的可擴展性。簡而言之,我們的op有一種定義,但可以有多種實現(xiàn)方式,并且這個實現(xiàn)的代碼可以不在框架內(nèi)部,這樣就使得框架在保持通用性的同時,易于在特定環(huán)境下做針對性的擴展。這套機制本質(zhì)上就是建立了一個字典,將op映射到函數(shù)指針,那么每次調(diào)用一個op的時候,我們可以根據(jù)一些標識(比如tensor.device)來判斷應(yīng)該調(diào)用哪一種實現(xiàn)。

PyTorch中提供了一個宏用來將實現(xiàn)注冊,從而讓dispatcher可以調(diào)用:

#define_TORCH_LIBRARY_IMPL(ns,k,m,uid)
staticvoidC10_CONCATENATE(
TORCH_LIBRARY_IMPL_init_##ns##_##k##_,uid)(torch::Library&);
staticconsttorch::TorchLibraryInitC10_CONCATENATE(
TORCH_LIBRARY_IMPL_static_init_##ns##_##k##_,uid)(
torch::IMPL,
c10::if_constexpr(
[](){
return&C10_CONCATENATE(
TORCH_LIBRARY_IMPL_init_##ns##_##k##_,uid);
},
[](){return[](torch::Library&)->void{};}),
#ns,
c10::k),
__FILE__,
__LINE__);
voidC10_CONCATENATE(
TORCH_LIBRARY_IMPL_init_##ns##_##k##_,uid)(torch::Library&m)

這個宏如果完全展開會是下面這樣:

staticvoidTORCH_LIBRARY_IMPL_init_aten_CPU_0(torch::Library&);
staticconsttorch::detail::TorchLibraryInitTORCH_LIBRARY_IMPL_static_init_aten_CPU_0(
torch::IMPL,
(c10::CPU)
?&TORCH_LIBRARY_IMPL_init_aten_CPU_0
:[](torch::Library&)->void{}),
"aten",
c10::CPU),
__FILE__,
__LINE__);
voidTORCH_LIBRARY_IMPL_init_aten_CPU_0(torch::Library&m)

這里比較需要注意的是第二行的TORCH_LIBRARY_IMPL_static_init_aten_CPU_0并不是一個函數(shù),而是一個靜態(tài)變量,它的作用就是在torch_xla庫初始化的時候,將xla定義的op注冊到PyTorch中。

關(guān)于這部分更詳細的介紹可以參考https://zhuanlan.zhihu.com/p/648578629。

從PyTorch調(diào)用到torch_xla

xla調(diào)用上面所說的宏進行注冊的位置在RegisterXLA.cpp這個文件中(codegen的結(jié)果),如下:

ORCH_LIBRARY_IMPL(aten,XLA,m){
m.impl("abs",
TORCH_FN(wrapper__abs));

...
}

其中,wrapper__abs的定義如下:

at::Tensorwrapper__abs(constat::Tensor&self){
returntorch_xla::abs(self);
}

顯然,這個定義和PyTorch框架內(nèi)部的算子是完全一致的,只是修改了實現(xiàn)。而XLANativeFunctions::abs的實現(xiàn)可以在aten_xla_type.cpp中找到,如下所示:

at::TensorXLANativeFunctions::abs(constat::Tensor&self){
XLA_FN_COUNTER("xla::");
returnbridge::abs(bridge::GetXlaTensor(self)));
}

到這里已經(jīng)比較明朗了,注冊之后,PyTorch上對于op的調(diào)用最終會進入torch_xla的native function中調(diào)用對應(yīng)的op實現(xiàn),而這些實現(xiàn)的根本都是對XLATensor進行操作,在最終操作執(zhí)行完成之后,會將作為結(jié)果的XLATensor重新轉(zhuǎn)換為torch Tensor,但要注意,這里的結(jié)果不一定被實際計算了,也可能只是記錄了一下IR,將節(jié)點加入圖中,這取決于具體的實現(xiàn)。

總結(jié)

其實torch-xla官方的文檔里是有關(guān)于代碼生成和算子注冊這個過程的描述的,只不過一開始我沒找到這個文檔,走了一點彎路,但是自己探索也會覺得更明了這個過程。官方文檔中的描述如下(節(jié)選):

All file mentioned below lives under the xla/torch_xla/csrc folder, with the exception of codegen/xla_native_functions.yaml

xla_native_functions.yaml contains the list of all operators that are lowered. Each operator name must directly match a pytorch operator listed in native_functions.yaml. This file serves as the interface to adding new xla operators, and is an input to PyTorch's codegen machinery. It generates the below 3 files: XLANativeFunctions.h, RegisterXLA.cpp, and RegisterAutogradXLA.cpp

XLANativeFunctions.h and aten_xla_type.cpp are entry points of PyTorch to the pytorch_xla world, and contain the manually written lowerings to XLA for each operator. XLANativeFunctions.h is auto-generated through a combination of xla_native_functions.yaml and the PyTorch core native_functions.yaml file, and contains declarations for kernels that need to be defined in aten_xla_type.cpp. The kernels written here need to construct 'XLATensor' using the input at::Tensor and other parameters. The resulting XLATensor needs to be converted back to the at::Tensor before returning to the PyTorch world.

RegisterXLA.cpp and RegisterAutogradXLA.cpp are auto-generated files that register all lowerings to the PyTorch Dispatcher. They also include auto-generated wrapper implementations of out= and inplace operators.

大概意思就是實際上torch-xla就是根據(jù)xla_native_functions.yaml這個文件來生成算子的定義,然后再生成對應(yīng)的RegisterXLA.cpp中的注冊代碼,這也跟PyTorch的codegen方式一致。

綜合這一整個過程可以看出,PyTorch是保持了高度的可擴展性的,不需要多少侵入式的修改就可以將所有的算子全部替換成自己的,這樣的方式也可以讓開發(fā)者不用去關(guān)注dispatcher及其上層的實現(xiàn),專注于算子本身的邏輯。

編輯:黃飛

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1653

    瀏覽量

    49774
  • 機器學(xué)習(xí)
    +關(guān)注

    關(guān)注

    66

    文章

    8482

    瀏覽量

    133923
  • 深度學(xué)習(xí)
    +關(guān)注

    關(guān)注

    73

    文章

    5550

    瀏覽量

    122373
  • pytorch
    +關(guān)注

    關(guān)注

    2

    文章

    809

    瀏覽量

    13712

原文標題:XLA和PyTorch的鏈接

文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦
    熱點推薦

    Pytorch自動求導(dǎo)示例

    Pytorch自動微分的幾個例子
    發(fā)表于 08-09 11:56

    TensorFlow XLA加速線性代數(shù)編譯器

    編譯:在會話級別中打開JIT編譯: 這是手動打開 JIT 編譯: 還可以通過將操作指定在特定的 XLA 設(shè)備(XLA_CPU 或 XLA_GPU)上,通過 XLA 來運行計算: Ao
    發(fā)表于 07-28 14:31

    excel vba代碼 示例講解

    excel vba代碼 示例講解
    發(fā)表于 09-07 09:36 ?25次下載
    excel vba<b class='flag-5'>代碼</b> <b class='flag-5'>示例</b>講解

    Pytorch入門教程與范例

    pytorch 是一個基于 python 的深度學(xué)習(xí)庫。pytorch 源碼庫的抽象層次少,結(jié)構(gòu)清晰,代碼量適中。相比于非常工程化的 tensorflow,pytorch 是一個更易入
    發(fā)表于 11-15 17:50 ?5544次閱讀
    <b class='flag-5'>Pytorch</b>入門教程與范例

    AD593X代碼示例

    AD593X代碼示例
    發(fā)表于 03-23 08:18 ?14次下載
    AD593X<b class='flag-5'>代碼</b><b class='flag-5'>示例</b>

    BeMicro代碼示例

    BeMicro代碼示例
    發(fā)表于 05-10 12:21 ?0次下載
    BeMicro<b class='flag-5'>代碼</b><b class='flag-5'>示例</b>

    ezLINX?示例PC應(yīng)用程序源代碼

    ezLINX?示例PC應(yīng)用程序源代碼
    發(fā)表于 06-05 19:12 ?1次下載
    ezLINX?<b class='flag-5'>示例</b>PC應(yīng)用程序源<b class='flag-5'>代碼</b>

    機器學(xué)習(xí)必學(xué)的Python代碼示例

    機器學(xué)習(xí)必學(xué)的Python代碼示例
    發(fā)表于 06-21 09:35 ?14次下載

    華為游戲服務(wù)示例代碼教程案例

    概述 游戲服務(wù)kit安卓示例代碼集成了華為游戲服務(wù)的眾多API,提供了示例代碼程序供您參考和使用,下面是對示例
    發(fā)表于 04-11 11:09 ?4次下載

    基于keil的AD7366示例代碼

    基于keil的AD7366示例代碼分享
    發(fā)表于 10-08 14:58 ?3次下載

    RAA489204 示例代碼軟件手冊

    RAA489204 示例代碼軟件手冊
    發(fā)表于 01-10 18:52 ?0次下載
    RAA489204 <b class='flag-5'>示例</b><b class='flag-5'>代碼</b>軟件手冊

    RAA489204 示例代碼軟件手冊

    RAA489204 示例代碼軟件手冊
    發(fā)表于 06-30 19:23 ?0次下載
    RAA489204 <b class='flag-5'>示例</b><b class='flag-5'>代碼</b>軟件手冊

    安全驅(qū)動示例代碼和實現(xiàn)

    示例代碼獲取和集成 本示例中的驅(qū)動只實現(xiàn)了對內(nèi)存的讀寫操作,并提供了測試使用的TA和CA。 讀者可使用如下指令從GitHub上獲取到示例代碼
    的頭像 發(fā)表于 10-30 16:07 ?817次閱讀
    安全驅(qū)動<b class='flag-5'>示例</b><b class='flag-5'>代碼</b>和實現(xiàn)

    自己編寫函數(shù)示例代碼很難嗎?分享幾個示例

    Q A 問: Arduino Uno的函數(shù)示例 我決定自己編寫函數(shù)示例代碼,因為這應(yīng)該是Arduino中的基本示例。網(wǎng)絡(luò)上確實有關(guān)于使用函數(shù)的文檔,但是,如果要嘗試使用
    的頭像 發(fā)表于 11-16 16:05 ?729次閱讀
    自己編寫函數(shù)<b class='flag-5'>示例</b><b class='flag-5'>代碼</b>很難嗎?分享幾個<b class='flag-5'>示例</b>!

    TorchFix:基于PyTorch代碼靜態(tài)分析

    TorchFix是我們最近開發(fā)的一個新工具,旨在幫助PyTorch用戶維護健康的代碼庫并遵循PyTorch的最佳實踐。首先,我想要展示一些我們努力解決的問題的示例。
    的頭像 發(fā)表于 12-18 15:20 ?1247次閱讀
    主站蜘蛛池模板: 激情综合色综合啪啪开心 | 国产又爽又黄又粗又大 | 国产一级在线观看 | 午夜伦y4480影院中文字幕 | 精品手机在线 | 久久综合九色欧美综合狠狠 | 亚洲综合第一区 | 老师下面好紧 | 欧美人与物另类 | 精品伊人久久大线蕉色首页 | 亚洲精品欧洲久久婷婷99 | 午夜剧院免费 | 欧美午夜视频在线观看 | 婷色| 天天想夜夜操 | 九九精品免费观看在线 | 在线看片成人 | 亚洲综合日韩欧美一区二区三 | 国产三级黄色录像 | 欧美777 | 可以免费看黄色的网站 | 国产一级在线观看 | 免费看你懂的 | 欧美日本一区二区 | 精品一区二区三区三区 | 国产三级精品三级在线观看 | 干干操| 天堂视频在线免费观看 | 久久久夜色精品国产噜噜 | 日韩欧美成人乱码一在线 | 成人特黄午夜性a一级毛片 成人网18免费下 | 久久久久88色偷偷免费 | 国产精品三级视频 | 午夜网站免费版在线观看 | 日毛片 | 久久免费观看国产精品 | 丁香久久婷婷 | 免费黄色三级 | 五月婷婷丁香花 | 操农村妇女 | 国产在线观看福利 |