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

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

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

3天內不再提示

如何使用張量核在CUDA C++設備代碼中編程

星星科技指導員 ? 來源:NVIDIA ? 作者:NVIDIA ? 2022-04-28 16:45 ? 次閱讀

新 Volta GPU 架構的一個定義性特征是它的 張量核 ,它使 Tesla V100 加速器的峰值吞吐量是上一代 Tesla P100 的 32 位浮點吞吐量的 12 倍。張量核心使人工智能程序員能夠使用 混合精度 來實現更高的吞吐量而不犧牲精度。

張量核心已經在主版本或許多深度學習框架(包括 PyTorch 、 TensorFlow 、 MXNet 和 Caffe2 )中通過 pull 請求支持 深度學習 培訓。有關在使用這些框架時啟用張量核心的更多信息,請查看 混合精度訓練指南 。

在這篇博客文章中,我們展示了如何使用 CUDA 庫在自己的應用程序中使用張量核,以及如何直接在 CUDA C ++設備代碼中編程。

什么是張量核?

Tesla V100 的張量核心是可編程的矩陣乘法和累加單元,可為訓練和推理應用提供多達 125 個張量 TFLOP 。 Tesla V100GPU 包含 640 個張量核心:每平方米 8 個。張量核心及其相關數據路徑都是定制的,可以顯著提高浮點計算吞吐量,只需適度的面積和功耗成本。時鐘門控廣泛用于最大限度地節省電力。

每個張量核提供一個 4x4x4 矩陣處理數組,該數組執行運算 D = A * B + C ,其中 答:, B 、 C 和 D 是 4 × 4 矩陣,如圖 1 所示。矩陣乘法輸入 A 和 B 是 FP16 矩陣,而累加矩陣 C 和 D 可以是 FP16 或 FP32 矩陣。

poYBAGJqVDeALjDyAABHkgRIl4s172.png

圖 1 :張量核 4x4x4 矩陣乘法和累加。

每個張量核心對每個時鐘執行 64 個浮點 FMA 混合精度運算( FP16 輸入乘法全精度乘積, FP32 累加,如圖 2 所示),一個 SM 中的 8 個張量核心每個時鐘執行 1024 個浮點運算。與使用標準 FP32 操作的 Pascal GP100 相比,每 SM 深度學習應用程序的吞吐量顯著提高了 8 倍,導致 Volta V100 GPU 的吞吐量比 Pascal P100 GPU 提高了 12 倍。張量核對 FP16 輸入數據進行 FP32 累加運算。對于 4x4x4 矩陣乘法, FP16 乘法會產生一個全精度的結果,該結果在 FP32 運算中與給定點積中的其他乘積累加,如圖 8 所示。

pYYBAGJqVDmAPS_jAAA73mD3jU8127.png

圖 2 : Volta GV100 張量核心操作。

在程序執行過程中,多個張量核被一個完整的執行過程并發使用。扭曲中的線程提供了一個更大的 16x16x16 矩陣運算,由張量核心處理。 CUDA 將這些操作暴露為 CUDA C ++ WMMA API 中的扭曲級別矩陣操作。這些 C ++接口提供專門的矩陣加載、矩陣乘法和累加運算以及矩陣存儲操作,以有效地利用 CUDA C ++程序中的張量核。

但是在我們深入了解張量核心的低級編程細節之前,讓我們看看如何通過 CUDA 庫訪問它們的性能。

CUDA 庫中的張量核

使用張量核的兩個 CUDA 庫是 cuBLAS 和 cuDNN 。 cuBLAS 使用張量核來加速 GEMM 計算( GEMM 是矩陣矩陣乘法的 BLAS 項); cuDNN 使用張量核來加速卷積和 遞歸神經網絡

許多計算應用都使用 GEMMs :信號處理、流體力學和許多其他的。隨著這些應用程序的數據大小呈指數級增長,這些應用程序需要匹配地提高處理速度。圖 3 中的混合精度 GEMM 性能圖表明張量核明確地滿足了這一需求。

提高卷積速度的需求同樣大;例如,今天的深度 神經網絡 ( DNNs )使用了許多層卷積。人工智能研究人員每年都在設計越來越深的神經網絡;現在最深的網絡中的卷積層數量已經有幾十個。訓練 dnn 需要在前向和反向傳播期間重復運行卷積層。圖 4 中的卷積性能圖顯示張量核滿足了卷積性能的需要。(您或許也對 混合精度神經網絡訓練的有效技術 上的這篇文章感興趣)

兩個性能圖表都顯示, Tesla V100 的張量核心的性能是上一代 Tesla P100 的數倍。性能改進這一巨大的改變了計算領域的工作方式:使交互成為可能,啟用“假設”場景研究,或者減少服務器場的使用。如果您在應用程序中使用 GEMMs 或卷積,請使用下面的簡單步驟來加速您的工作。

如何在 cuBLAS 中使用張量核

您可以利用張量核心,對現有的 cuBLAS 代碼進行一些更改。這些更改是您使用 cuBLAS API 時所做的微小更改。

下面的示例代碼應用了一些簡單的規則來指示 cuBLAS 應該使用張量核;這些規則在代碼后面顯式地枚舉。

示例代碼

下面的代碼在很大程度上與以前的架構上用于調用 cuBLAS 中 GEMM 的通用代碼相同。

下面的代碼在很大程度上與以前的架構上用于調用 cuBLAS 中 GEMM 的通用代碼相同。

// First, create a cuBLAS handle:
cublasStatus_t cublasStat = cublasCreate(&handle); // Set the math mode to allow cuBLAS to use Tensor Cores:
cublasStat = cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH); // Allocate and initialize your matrices (only the A matrix is shown):
size_t matrixSizeA = (size_t)rowsA * colsA;
T_ELEM_IN **devPtrA = 0; cudaMalloc((void**)&devPtrA[0], matrixSizeA * sizeof(devPtrA[0][0]));
T_ELEM_IN A = (T_ELEM_IN *)malloc(matrixSizeA * sizeof(A[0])); memset( A, 0xFF, matrixSizeA* sizeof(A[0]));
status1 = cublasSetMatrix(rowsA, colsA, sizeof(A[0]), A, rowsA, devPtrA[i], rowsA); // ... allocate and initialize B and C matrices (not shown) ... // Invoke the GEMM, ensuring k, lda, ldb, and ldcare all multiples of 8, // and m is a multiple of 4:
cublasStat = cublasGemmEx(handle, transa, transb, m, n, k, alpha, A, CUDA_R_16F, lda, B, CUDA_R_16F, ldb, beta, C, CUDA_R_16F, ldc, CUDA_R_32F, algo);

一些簡單的規則

cuBLAS 用戶會注意到他們現有的 cuBLAS GEMM 代碼有一些變化:

例程必須是 GEMM ;目前,只有 GEMM 支持 Tensor 核心執行。

數學模式必須設置為 CUBLAS_TENSOR_OP_MATH 。浮點數學是非關聯的,因此張量核心數學例程的結果與類似的非張量核心數學例程的結果不完全對等。 cuBLAS 要求用戶選擇使用張量核。

k 、 lda 、 ldb 和 ldc 都必須是 8 的倍數; m 必須是 4 的倍數。張量核心數學例程以八個值的步長跨越輸入數據,因此矩陣的維數必須是 8 的倍數。

矩陣的輸入和輸出數據類型必須是半精度或單精度。(上面只顯示了 CUDA_R_16F ,但也支持 CUDA_R_32F 。)

不滿足上述規則的 gemm 將返回到非張量核心實現。

GEMM 性能

如前所述, Tensor 內核提供的 GEMM 性能是以前硬件的數倍。圖 3 顯示了 GP100 ( Pascal )與 GV100 ( Volta )硬件的比較性能。

圖 3 。使用張量核的 Tesla V100 ( Volta )與 Tesla P100 ( Pascal )的矩陣矩陣乘法( GEMM )的性能比較。輸入矩陣是半精度的,計算是單精度的。

如何在 cuDNN 中使用張量核

在 cuDNN 中使用張量核也很簡單,而且只涉及對現有代碼的細微更改。

示例代碼

在 cuDNN 中使用張量核心的示例代碼可以在 cuDNN samples 目錄的 conv_sample.cpp 中找到;我們復制了下面的一些摘錄。( cuDNN 樣本目錄 與文檔一起打包。)

// Create a cuDNN handle:
checkCudnnErr(cudnnCreate(&handle_)); // Create your tensor descriptors:
checkCudnnErr( cudnnCreateTensorDescriptor( &cudnnIdesc ));
checkCudnnErr( cudnnCreateFilterDescriptor( &cudnnFdesc ));
checkCudnnErr( cudnnCreateTensorDescriptor( &cudnnOdesc ));
checkCudnnErr( cudnnCreateConvolutionDescriptor( &cudnnConvDesc )); // Set tensor dimensions as multiples of eight (only the input tensor is shown here):
int dimA[] = {1, 8, 32, 32};
int strideA[] = {8192, 1024, 32, 1}; checkCudnnErr( cudnnSetTensorNdDescriptor(cudnnIdesc, getDataType(), convDim+2, dimA, strideA) ); // Allocate and initialize tensors (again, only the input tensor is shown):
checkCudaErr( cudaMalloc((void**)&(devPtrI), (insize) * sizeof(devPtrI[0]) ));
hostI = (T_ELEM*)calloc (insize, sizeof(hostI[0]) ); initImage(hostI, insize); checkCudaErr( cudaMemcpy(devPtrI, hostI, sizeof(hostI[0]) * insize, cudaMemcpyHostToDevice)); // Set the compute data type (below as CUDNN_DATA_FLOAT):
checkCudnnErr( cudnnSetConvolutionNdDescriptor(cudnnConvDesc, convDim, padA, convstrideA, dilationA, CUDNN_CONVOLUTION, CUDNN_DATA_FLOAT) ); // Set the math type to allow cuDNN to use Tensor Cores:
checkCudnnErr( cudnnSetConvolutionMathType(cudnnConvDesc, CUDNN_TENSOR_OP_MATH) ); // Choose a supported algorithm:
cudnnConvolutionFwdAlgo_t algo = CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM; // Allocate your workspace:
checkCudnnErr( cudnnGetConvolutionForwardWorkspaceSize(handle_, cudnnIdesc, cudnnFdesc, cudnnConvDesc, cudnnOdesc, algo, &workSpaceSize) ); if (workSpaceSize > 0) { cudaMalloc(&workSpace, workSpaceSize);
} // Invoke the convolution:
checkCudnnErr( cudnnConvolutionForward(handle_, (void*)(&alpha), cudnnIdesc, devPtrI, cudnnFdesc, devPtrF, cudnnConvDesc, algo, workSpace, workSpaceSize, (void*)(&beta), cudnnOdesc, devPtrO) );

一些簡單的規則

注意一些與普通 cuDNN 用法不同的地方:

卷積算法必須是 ALGO_1 ( IMPLICIT_PRECOMP_GEMM 表示正向)。除了 ALGO_1 之外的其他卷積算法可能在未來的 cuDNN 版本中使用張量核。

數學類型必須設置為 CUDNN_TENSOR_OP_MATH 。與 cuBLAS 一樣,張量核心數學例程的結果與類似的非張量核心數學例程的結果并不完全等價,因此 cuDNN 要求用戶“選擇”使用張量核心。

輸入和輸出通道尺寸都必須是 8 的倍數。同樣,在 cuBLAS 中,張量核心數學例程以八個值的步長跨越輸入數據,因此輸入數據的維數必須是 8 的倍數。

卷積的輸入、過濾和輸出數據類型必須為半精度。

不滿足上述規則的卷積將返回到非張量核心實現。

上面的示例代碼顯示了 NCHW 數據格式,請參見 conv_sample.cpp NHWC 支持示例。

卷積性能

如前所述,張量核心的卷積性能是以前硬件的數倍。圖 4 顯示了 GP100 ( Pascal )與 GV100 ( Volta )硬件的比較性能。

圖 4 。張量核的 Tesla V100 ( Volta )卷積與 Tesla P100 ( Pascal )卷積的性能比較。比較來自每個神經網絡的 卷積 層運行時間的幾何平均值。 V100 和 P100 都使用 FP16 輸入/輸出數據和 FP32 計算; V100 使用張量核心,而 P100 使用 FP32 融合乘法加法( FMA )。

CUDA 9.0 中張量核的編程訪問

通過 CUDA 9.0 訪問內核中的張量核是一個預覽功能。這意味著本節中描述的數據結構、 api 和代碼在未來的 CUDA 版本中可能會發生變化。

雖然 cuBLAS 和 cuDNN 覆蓋了張量核的許多潛在用途,但是您也可以直接在 nvcuda::wmma C ++中編程它們。張量核心通過 CUDA 命名空間中的一組函數和類型在 CUDA 9 。 0 中公開。它們允許您將值加載或初始化為張量核心所需的特殊格式,執行矩陣乘法累加( MMA )步驟,并將值存儲回內存。在程序執行過程中,一個完整的扭曲同時使用多個張量核。這允許 warp 在非常高的吞吐量下執行 16x16x16mma (圖 5 )。

圖 5 : warp 執行 D = A * B + C ,其中 A 、 B 、 C 和 D 是 16 × 16 矩陣。(注意圖 1 中編號的變化:多個張量核心操作由 WMMA API 組合,以執行 16 × 16 矩陣乘法和累加運算。)

讓我們看一個簡單的例子,它展示了如何使用 WMMA ( Warp Matrix Multiply Accumulate ) API 來執行矩陣乘法。注意,這個例子并沒有針對高性能進行調整,主要是作為 API 的演示。為了獲得更好的性能, MIG ht 應用于此代碼的優化示例,請查看 CUDA 工具箱中的 cudaTensorCoreGemm 示例。為了獲得最高的生產性能,應該使用 cuBLAS 代碼,如上所述。

標題和命名空間

WMMA API 包含在 mma.h 頭文件中。完整的名稱空間是 nvcuda::wmma::* ,但是在代碼中保持 wmma 的顯式是很有用的,所以我們只使用 nvcuda 名稱空間。

#include 
using namespace nvcuda;

設計和初始化

完整的 GEMM 規范允許算法處理 a 或 b 的換位,并使數據跨距大于矩陣中的跨距。為了簡單起見,讓我們假設 a 和 b 都不是換位的,并且內存和矩陣的前導維度是相同的。

我們將采用的策略是讓一個 warp 負責輸出矩陣的單個 16 × 16 部分。通過使用二維網格和線程塊,我們可以有效地在二維輸出矩陣上平鋪扭曲。

// The only dimensions currently supported by WMMA
const int WMMA_M = 16;
const int WMMA_N = 16;
const int WMMA_K = 16; __global__ void wmma_example(half *a, half *b, float *c, int M, int N, int K, float alpha, float beta) { // Leading dimensions. Packed with no transpositions. int lda = M; int ldb = K; int ldc = M; // Tile using a 2D grid int warpM = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize; int warpN = (blockIdx.y * blockDim.y + threadIdx.y);

在執行 MMA 操作之前,操作數矩陣必須在 GPU 的寄存器中表示。由于 MMA 是一個 warp 范圍的操作,這些寄存器分布在 warp 的線程中,每個線程持有整個矩陣的 片段 。單個矩陣參數與片段之間的映射是不透明的,因此您的程序不應對此進行假設。

在 CUDA 中,片段是一種模板化類型,其模板參數描述了片段持有的矩陣( a 、 B 或累加器)、整體 WMMA 操作的形狀、數據類型,以及對于 a 和 B 矩陣,數據是行還是列主。最后一個參數可用于執行 A 或 B 矩陣的換位。這個例子沒有換位,所以兩個矩陣都是列 major ,這是 GEMM 的標準。

 // Declare the fragments wmma::fragment a_frag; wmma::fragment b_frag; wmma::fragment acc_frag; wmma::fragment c_frag;

初始化步驟的最后一部分是用零填充累加器片段。

 wmma::fill_fragment(acc_frag, 0.0f);

內環

我們用一個矩陣來計算每一個輸出的扭曲策略。為此,我們需要循環 A 矩陣的行和 B 矩陣的列。這是沿著兩個矩陣的 K 維生成一個 MxN 輸出塊。 loadmatrix 函數從內存(在本例中是全局內存,盡管可以是任何內存空間)中獲取數據并將其放入片段中。加載的第三個參數是矩陣內存中的“前導維度”;我們加載的 16 × 16 塊在內存中是不連續的,因此函數需要知道連續列(或行,如果這些是行的主要片段)之間的跨距。

MMA 調用就地累積,因此第一個參數和最后一個參數都是我們先前初始化為零的累加器片段。

 // Loop over the K-dimension for (int i = 0; i < K; i += WMMA_K) { int aRow = warpM * WMMA_M; int aCol = i; int bRow = i; int bCol = warpN * WMMA_N; // Bounds checking if (aRow < M && aCol < K && bRow < K && bCol < N) { // Load the inputs wmma::load_matrix_sync(a_frag, a + aRow + aCol * lda, lda); wmma::load_matrix_sync(b_frag, b + bRow + bCol * ldb, ldb); // Perform the matrix multiplication wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag); } }

完成

acc_frag 現在基于 A 和 B 的乘法保存此扭曲的輸出塊的結果。完整的 GEMM 規范允許縮放此結果,并將其累積到適當的矩陣頂部。實現這種縮放的一種方法是對片段執行元素級操作。雖然沒有定義從矩陣坐標到線程的映射,但是元素級操作不需要知道這個映射,所以仍然可以使用片段來執行。因此,對片段執行縮放操作或將一個片段的內容添加到另一個片段是合法的,只要這兩個片段具有相同的模板參數。如果片段具有不同的模板參數,則結果未定義。使用這個特性,我們將現有的數據加載到 C 語言中,并使用正確的縮放比例來累積到目前為止的計算結果。

 // Load in current value of c, scale by beta, and add to result scaled by alpha int cRow = warpM * WMMA_M; int cCol = warpN * WMMA_N; if (cRow < M && cCol < N) { wmma::load_matrix_sync(c_frag, c + cRow + cCol * ldc, ldc, wmma::mem_col_major); for(int i=0; i < c_frag.num_elements; i++) { c_frag.x[i] = alpha * acc_frag.x[i] + beta * c_frag.x[i]; }

最后,我們將數據存儲到內存中。同樣,目標指針可以是 GPU 可見的任何內存空間,并且必須指定內存中的前導維度。還有一個選項可以指定輸出是寫在行還是列 major 。

 // Store the output wmma::store_matrix_sync(c + cRow + cCol * ldc, c_frag, ldc, wmma::mem_col_major); }
}

這樣,矩陣乘法就完成了。我在這篇博文中省略了主機代碼,不過是一個 完整的工作示例可以在 Github 上找到 。

今天就從 CUDA 9 中的張量核心開始吧

希望這個例子能讓您了解如何在應用程序中使用張量核。

關于作者

Jeremy Appleyard 是 NVIDIA 歐洲開發人員技術團隊的一名開發人員。他位于英國牛津附近,與開發人員一起加速 GPUs 上的應用程序。他擁有克蘭菲爾德大學計算流體力學博士學位。

Scott Yokim 是 NVIDIA 的 CUDA 庫團隊的高級軟件工程師。他于 2008 年加入 NVIDIA ,在此之前,他是多家公司的計算機圖形程序員。斯科特擁有弗吉尼亞理工大學數學碩士學位。

審核編輯:郭婷

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 神經網絡
    +關注

    關注

    42

    文章

    4775

    瀏覽量

    100920
  • 人工智能
    +關注

    關注

    1792

    文章

    47445

    瀏覽量

    239060
  • CUDA
    +關注

    關注

    0

    文章

    121

    瀏覽量

    13644
收藏 人收藏

    評論

    相關推薦

    AKI跨語言調用庫神助攻C/C++代碼遷移至HarmonyOS NEXT

    產品創新與功能迭代,而非技術遷移的細節問題,大幅提升開發效率。 據悉,涉及C/C++/ETS跨越語言調用的鴻蒙化應用,有超過80%的項目都在使用AKI,如某知名購物應用,使用后減少
    發表于 01-02 17:08

    RK3568國產處理器 + TensorFlow框架的張量創建實驗案例分享

    張量,即標量 2、一 維張量 3、二維張量 4、多維張量 tensorflow
    發表于 12-03 14:43

    C++新手容易犯的十個編程錯誤

    簡單的總結一下?C++ 新手容易犯的一些編程錯誤,給新人們提供一個參考。 1 有些關鍵字 cpp 文件多寫了 對于 C++ 類,一些關鍵
    的頭像 發表于 11-15 12:42 ?420次閱讀

    C語言和C++結構體的區別

    同樣是結構體,看看在C語言和C++中有什么區別?
    的頭像 發表于 10-30 15:11 ?290次閱讀

    ostreamc++的用法

    ostream 是 C++ 標準庫中一個非常重要的類,它位于 頭文件(實際上,更常見的是通過包含 頭文件來間接包含 ,因為 包含了 和 )。 ostream 類及其派生類(如 std::cout
    的頭像 發表于 09-20 15:11 ?811次閱讀

    ModusToolbox 3.2c代碼包含c++代碼的正確步驟是什么?

    文件,但要在 main.c #include 它們時 會導致構建失敗。 將 main.c 重命名為 main.cpp 會導致標準 XMC 庫函數(如 XMC_GPIO_SetMode)中出現許多錯誤。
    發表于 07-23 08:21

    C++實現類似instanceof的方法

    函數,可實際上C++沒有。但是別著急,其實C++中有兩種簡單的方法可以實現類似Java的instanceof的功能。
    的頭像 發表于 07-18 10:16 ?619次閱讀
    <b class='flag-5'>C++</b><b class='flag-5'>中</b>實現類似instanceof的方法

    OpenCV圖像識別C++代碼

    的頭文件 您的C++代碼,包含以下必要的頭文件: # include # include # include # include # include # include # inc
    的頭像 發表于 07-16 10:42 ?2153次閱讀

    C/C++兩種宏實現方式

    #ifndef的方式受C/C++語言標準支持。它不僅可以保證同一個文件不會被包含多次,也能保證內容完全相同的兩個文件(或者代碼片段)不會被不小心同時包含。
    的頭像 發表于 04-19 11:50 ?666次閱讀

    C/C++代碼動態測試工具VectorCAST插樁功能演示#代碼動態測試 #C++

    C++代碼
    北匯信息POLELINK
    發布于 :2024年04月18日 11:57:45

    使用 MISRA C++:2023? 避免基于范圍的 for 循環中的錯誤

    在前兩篇博客,我們?向您介紹了新的 MISRA C++ 標準?和?C++ 的歷史?。在這篇博客,我們將仔細研究以 C++
    的頭像 發表于 03-28 13:53 ?828次閱讀
    使用 MISRA <b class='flag-5'>C++</b>:2023? 避免基于范圍的 for 循環中的錯誤

    為什么很少用C++開發單片機

    C語言是面向過程的語言,C++是面向對象的編程語言。結合本文來說,面向過程相比面向對象的編程,生成代碼量(bin文件)更小,運行效率更高。
    發表于 03-25 14:26 ?1055次閱讀
    為什么很少用<b class='flag-5'>C++</b>開發單片機

    CYUSB2014固件編程后,出現“此設備無法啟動”的原因?如何解決?

    我們原型板中使用 CYUSB2014芯片。 啟動模式是通過 USB。 使用自定義 C++ 軟件,我將固件下載到設備(RAM 模式)。 我使用 s \" laveFifo \"
    發表于 02-26 07:55

    c語言,c++,java,python區別

    操作系統、嵌入式系統等對性能要求較高的場景。C語言的語法相對簡單,學習曲線較平緩,也是學習其他高級語言的入門語言。 C++C++C
    的頭像 發表于 02-05 14:11 ?2479次閱讀

    vb語言和c++語言的區別

    VB語言和C++語言是兩種不同的編程語言,雖然它們都屬于高級編程語言,但在設計和用途上有很多區別。下面將詳細比較VB語言和C++語言的區別。 設計目標: VB語言(Visual Bas
    的頭像 發表于 02-01 10:20 ?2395次閱讀
    主站蜘蛛池模板: 国产黄色在线观看| 4455四色永久免费| 亚洲国产系列| 井野雏田小樱天天被调教| 欧美黄色高清| 一区二区影视| 99精品在线| 午夜看片a福利在线| 四虎久久影院| 日本aaaa| 韩国最新三级网站在线播放| 涩涩涩丁香色婷五月网视色| 日韩一级免费视频| 欧美色视频日本| 日韩免费观看的一级毛片| 色多多在线观看| 精品国产一二三区在线影院| 岛国片欧美一级毛片| 五月天婷婷网站| 福利一区在线观看| 亚洲精品美女久久久aaa| 久久精品亚洲热综合一本奇米| 五月婷婷六月合| 久久青草91免费观看| bt天堂资源种子在线| 天堂资源地址在线| 天天狠狠干| 成人夜色| 69japanese日本100| 亚洲免费视频网址| 性欧美一级| 激情网页| 日韩天天操| 中文字幕一二三区| 亚洲人成电影在线播放| 欧美www| 尤物久久99热国产综合| 亚洲国产片| 福利一级片| 在线久综合色手机在线播放| 欧美香蕉视频|