diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 40c4cacf91de61c19291d33559878411f0eef72b..167224a7153bdd0e8f57204c2ddcd9226a4483bb 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -50,6 +50,8 @@
 #include <assert.h>
 #include <cassert>
 
+#define JVET_L0090_PAIR_AVG                               1 // Add pairwise average candidates, replace HEVC combined candidates
+
 #define JVET_L0392_ALF_INIT_STATE                         1
 
 #define JVET_L0664_ALF_REMOVE_LUMA_5x5                    1
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index feb106da69a7e27d1bf3a27f4015b3240c911d77..8497704fb108392a82aa431fb43beec79311e7e2 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -502,11 +502,16 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
   const uint32_t maxNumMergeCand = slice.getMaxNumMergeCand();
   const bool canFastExit     = pu.cs->pps->getLog2ParallelMergeLevelMinus2() == 0;
 
+#if !JVET_L0090_PAIR_AVG
+  // this variable is unused if remove HEVC combined candidates
   bool isCandInter[MRG_MAX_NUM_CANDS];
+#endif
 
   for (uint32_t ui = 0; ui < maxNumMergeCand; ++ui)
   {
+#if !JVET_L0090_PAIR_AVG
     isCandInter[ui] = false;
+#endif
     mrgCtx.interDirNeighbours[ui] = 0;
     mrgCtx.mrgTypeNeighbours [ui] = MRG_TYPE_DEFAULT_N;
     mrgCtx.mvFieldNeighbours[(ui << 1)    ].refIdx = NOT_VALID;
@@ -532,7 +537,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
   {
     miLeft = puLeft->getMotionInfo( posLB.offset(-1, 0) );
 
+#if !JVET_L0090_PAIR_AVG
     isCandInter[cnt] = true;
+#endif
 
     // get Inter Dir
     mrgCtx.interDirNeighbours[cnt] = miLeft.interDir;
@@ -570,7 +577,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
 
     if( !isAvailableA1 || ( miAbove != miLeft ) )
     {
+#if !JVET_L0090_PAIR_AVG
       isCandInter[cnt] = true;
+#endif
 
       // get Inter Dir
       mrgCtx.interDirNeighbours[cnt] = miAbove.interDir;
@@ -612,7 +621,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
     if( !isAvailableB1 || ( miAbove != miAboveRight ) )
 #endif
     {
+#if !JVET_L0090_PAIR_AVG
       isCandInter[cnt] = true;
+#endif
 
       // get Inter Dir
       mrgCtx.interDirNeighbours[cnt] = miAboveRight.interDir;
@@ -653,7 +664,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
     if( !isAvailableA1 || ( miBelowLeft != miLeft ) )
 #endif
     {
+#if !JVET_L0090_PAIR_AVG
       isCandInter[cnt] = true;
+#endif
 
       // get Inter Dir
       mrgCtx.interDirNeighbours[cnt] = miBelowLeft.interDir;
@@ -700,7 +713,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
 
     if( isAvailableSubPu )
     {
+#if !JVET_L0090_PAIR_AVG
       isCandInter[cnt] = true;
+#endif
 
       mrgCtx.mrgTypeNeighbours[cnt] = MRG_TYPE_SUBPU_ATMVP;
 
@@ -736,7 +751,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
       if( ( !isAvailableA1 || ( miLeft != miAboveLeft ) ) && ( !isAvailableB1 || ( miAbove != miAboveLeft ) ) )
 #endif
       {
+#if !JVET_L0090_PAIR_AVG
         isCandInter[cnt] = true;
+#endif
 
         // get Inter Dir
         mrgCtx.interDirNeighbours[cnt] = miAboveLeft.interDir;
@@ -867,7 +884,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
       if( addTMvp )
       {
         mrgCtx.interDirNeighbours[uiArrayAddr] = dir;
+#if !JVET_L0090_PAIR_AVG
         isCandInter              [uiArrayAddr] = true;
+#endif
 
         if( mrgCandIdx == cnt && canFastExit )
         {
@@ -885,7 +904,97 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
     return;
   }
 
+#if JVET_L0090_PAIR_AVG
+  // pairwise-average candidates
+  {
+    const int cutoff = std::min( cnt, 4 );
+    const int end = cutoff * (cutoff - 1) / 2;
+    constexpr int PRIORITY_LIST0[] = { 0, 0, 1, 0, 1, 2 };
+    constexpr int PRIORITY_LIST1[] = { 1, 2, 2, 3, 3, 3 };
+
+    for( int idx = 0; idx < end && cnt != maxNumMergeCand; idx++ )
+    {
+      const int i = PRIORITY_LIST0[idx];
+      const int j = PRIORITY_LIST1[idx];
+
+      mrgCtx.mvFieldNeighbours[cnt * 2].setMvField( Mv( 0, 0 ), NOT_VALID );
+      mrgCtx.mvFieldNeighbours[cnt * 2 + 1].setMvField( Mv( 0, 0 ), NOT_VALID );
+      // calculate average MV for L0 and L1 seperately
+      unsigned char interDir = 0;
+      for( int refListId = 0; refListId < (slice.isInterB() ? 2 : 1); refListId++ )
+      {
+        const short refIdxI = mrgCtx.mvFieldNeighbours[i * 2 + refListId].refIdx;
+        const short refIdxJ = mrgCtx.mvFieldNeighbours[j * 2 + refListId].refIdx;
+
+        // both MVs are invalid, skip
+        if( (refIdxI == NOT_VALID) && (refIdxJ == NOT_VALID) )
+        {
+          continue;
+        }
+
+        interDir += 1 << refListId;
+        // both MVs are valid, average these two MVs
+        if( (refIdxI != NOT_VALID) && (refIdxJ != NOT_VALID) )
+        {
+          const Mv& MvI = mrgCtx.mvFieldNeighbours[i * 2 + refListId].mv;
+          const Mv& MvJ = mrgCtx.mvFieldNeighbours[j * 2 + refListId].mv;
+
+          // average two MVs
+          Mv avgMv = MvI;
+#if !REMOVE_MV_ADAPT_PREC
+          if( pu.cs->sps->getSpsNext().getUseHighPrecMv() )
+          {
+            avgMv.setHighPrec();
+          }
+#endif
+          avgMv += MvJ;
+          avgMv.setHor( avgMv.getHor() / 2 );
+          avgMv.setVer( avgMv.getVer() / 2 );
+
+          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( avgMv, refIdxI );
+        }
+        // only one MV is valid, take the only one MV
+        else if( refIdxI != NOT_VALID )
+        {
+          Mv singleMv = mrgCtx.mvFieldNeighbours[i * 2 + refListId].mv;
+#if !REMOVE_MV_ADAPT_PREC
+          if( pu.cs->sps->getSpsNext().getUseHighPrecMv() )
+          {
+            singleMv.setHighPrec();
+          }
+#endif
+          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxI );
+        }
+        else if( refIdxJ != NOT_VALID )
+        {
+          Mv singleMv = mrgCtx.mvFieldNeighbours[j * 2 + refListId].mv;
+#if !REMOVE_MV_ADAPT_PREC
+          if( pu.cs->sps->getSpsNext().getUseHighPrecMv() )
+          {
+            singleMv.setHighPrec();
+          }
+#endif
+          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxJ );
+        }
+      }
+
+      mrgCtx.interDirNeighbours[cnt] = interDir;
+      if( interDir > 0 )
+      {
+        cnt++;
+      }
+    }
+
+    // early termination
+    if( cnt == maxNumMergeCand )
+    {
+      return;
+    }
+  }
+#endif
+
   uint32_t uiArrayAddr = cnt;
+#if !JVET_L0090_PAIR_AVG
   uint32_t uiCutoff    = std::min( uiArrayAddr, 4u );
 
   if (slice.isInterB())
@@ -928,6 +1037,7 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
   {
     return;
   }
+#endif
 
   int iNumRefIdx = slice.isInterB() ? std::min(slice.getNumRefIdx(REF_PIC_LIST_0), slice.getNumRefIdx(REF_PIC_LIST_1)) : slice.getNumRefIdx(REF_PIC_LIST_0);
 
@@ -935,7 +1045,9 @@ void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx, co
   int refcnt = 0;
   while (uiArrayAddr < maxNumMergeCand)
   {
+#if !JVET_L0090_PAIR_AVG
     isCandInter               [uiArrayAddr     ] = true;
+#endif
     mrgCtx.interDirNeighbours [uiArrayAddr     ] = 1;
     mrgCtx.mvFieldNeighbours  [uiArrayAddr << 1].setMvField(Mv(0, 0), r);