diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 54cfe178e2cfab8cf953d86c3f2a5ab06c59bc3b..b5d6ad17068129e75a6f7ff423780becb0a92f25 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -257,6 +257,10 @@ void EncApp::xInitLibCfg()
 #endif  
   m_cEncLib.setUseMHIntra                                        ( m_MHIntra );
   m_cEncLib.setUseTriangle                                       ( m_Triangle );
+#if JVET_M0253_HASH_ME
+  m_cEncLib.setUseHashME                                         ( m_HashME );
+#endif
+
 #if JVET_M0255_FRACMMVD_SWITCH
   m_cEncLib.setAllowDisFracMMVD                                  ( m_allowDisFracMMVD );
 #endif
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index f31fad8636275253c227ef20c31d5251b929fa5e..b79df7b356c4edc63d4d5edd172393362df1749c 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -864,6 +864,10 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 #endif
   ("MHIntra",                                         m_MHIntra,                                        false, "Enable MHIntra mode")
   ("Triangle",                                        m_Triangle,                                       false, "Enable triangular shape motion vector prediction (0:off, 1:on)")
+#if JVET_M0253_HASH_ME
+  ("HashME",                                          m_HashME,                                         false, "Enable hash motion estimation (0:off, 1:on)")
+#endif
+
 #if JVET_M0255_FRACMMVD_SWITCH
   ("AllowDisFracMMVD",                                m_allowDisFracMMVD,                               false, "Disable fractional MVD in MMVD mode adaptively")
 #endif
@@ -1943,6 +1947,9 @@ bool EncAppCfg::xCheckParameter()
     xConfirmPara( m_MTT, "Multi type tree is only allowed with NEXT profile" );
     xConfirmPara( m_ImvMode, "IMV is only allowed with NEXT profile" );
     xConfirmPara(m_IBCMode, "IBC Mode only allowed with NEXT profile");
+#if JVET_M0253_HASH_ME
+    xConfirmPara( m_HashME, "Hash motion estimation only allowed with NEXT profile" );
+#endif   
     xConfirmPara( m_useFastLCTU, "Fast large CTU can only be applied when encoding with NEXT profile" );
 #if JVET_M0464_UNI_MTS
     xConfirmPara( m_MTS, "MTS only allowed with NEXT profile" );
@@ -3165,6 +3172,9 @@ void EncAppCfg::xPrintParameter()
 #endif
   }
     msg(VERBOSE, "IBC:%d ", m_IBCMode);
+#if JVET_M0253_HASH_ME
+  msg( VERBOSE, "HashME:%d ", m_HashME );
+#endif
   msg( VERBOSE, "WrapAround:%d ", m_wrapAround);
   if( m_wrapAround )
   {
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index c91c7ec580402ba1b0a7941bb7222fb0e4404b7f..b7df630eb5e7fb3aeb7419b3f69d561b2eb73f6c 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -237,6 +237,9 @@ protected:
 
   bool      m_MHIntra;
   bool      m_Triangle;
+#if JVET_M0253_HASH_ME
+  bool      m_HashME;
+#endif
 #if JVET_M0255_FRACMMVD_SWITCH
   bool      m_allowDisFracMMVD;
 #endif
diff --git a/source/Lib/CommonLib/Hash.cpp b/source/Lib/CommonLib/Hash.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4dba4d867293dc18e850da56fe39dc324567d243
--- /dev/null
+++ b/source/Lib/CommonLib/Hash.cpp
@@ -0,0 +1,732 @@
+/* The copyright in this software is being made available under the BSD
+ * License, included below. This software may be subject to other third party
+ * and contributor rights, including patent rights, and no such rights are
+ * granted under this license.
+ *
+ * Copyright (c) 2010-2019, ITU/ISO/IEC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+ *    be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /** \file     TEncHash.cpp
+     \brief    hash encoder class
+ */
+#include "CommonLib/dtrace_codingstruct.h"
+#include "CommonLib/Picture.h"
+#include "CommonLib/UnitTools.h"
+#include "Hash.h"
+
+
+
+ // ====================================================================================================================
+ // Constructor / destructor / create / destroy
+ // ====================================================================================================================
+
+int TComHash::m_blockSizeToIndex[65][65];
+TCRCCalculatorLight TComHash::m_crcCalculator1(24, 0x5D6DCB);
+TCRCCalculatorLight TComHash::m_crcCalculator2(24, 0x864CFB);
+
+TCRCCalculatorLight::TCRCCalculatorLight(unsigned int bits, unsigned int truncPoly)
+{
+  m_remainder = 0;
+  m_bits = bits;
+  m_truncPoly = truncPoly;
+  m_finalResultMask = (1 << bits) - 1;
+
+  xInitTable();
+}
+
+TCRCCalculatorLight::~TCRCCalculatorLight()
+{
+
+}
+
+void TCRCCalculatorLight::xInitTable()
+{
+  const unsigned int highBit = 1 << (m_bits - 1);
+  const unsigned int ByteHighBit = 1 << (8 - 1);
+
+  for (unsigned int value = 0; value < 256; value++)
+  {
+    unsigned int remainder = 0;
+    for (unsigned char mask = ByteHighBit; mask != 0; mask >>= 1)
+    {
+      if (value & mask)
+      {
+        remainder ^= highBit;
+      }
+
+      if (remainder & highBit)
+      {
+        remainder <<= 1;
+        remainder ^= m_truncPoly;
+      }
+      else
+      {
+        remainder <<= 1;
+      }
+    }
+
+    m_table[value] = remainder;
+  }
+}
+
+void TCRCCalculatorLight::processData(unsigned char* curData, unsigned int dataLength)
+{
+  for (unsigned int i = 0; i < dataLength; i++)
+  {
+    unsigned char index = (m_remainder >> (m_bits - 8)) ^ curData[i];
+    m_remainder <<= 8;
+    m_remainder ^= m_table[index];
+  }
+}
+
+
+TComHash::TComHash()
+{
+  m_lookupTable = NULL;
+  tableHasContent = false;
+}
+
+TComHash::~TComHash()
+{
+  clearAll();
+  if (m_lookupTable != NULL)
+  {
+    delete[] m_lookupTable;
+    m_lookupTable = NULL;
+  }
+}
+
+void TComHash::create()
+{
+  if (m_lookupTable != NULL)
+  {
+    clearAll();
+    return;
+  }
+  int maxAddr = 1 << (m_CRCBits + m_blockSizeBits);
+  m_lookupTable = new std::vector<BlockHash>*[maxAddr];
+  memset(m_lookupTable, 0, sizeof(std::vector<BlockHash>*) * maxAddr);
+  tableHasContent = false;
+}
+
+void TComHash::clearAll()
+{
+  tableHasContent = false;
+  if (m_lookupTable == NULL)
+  {
+    return;
+  }
+  int maxAddr = 1 << (m_CRCBits + m_blockSizeBits);
+  for (int i = 0; i < maxAddr; i++)
+  {
+    if (m_lookupTable[i] != NULL)
+    {
+      delete m_lookupTable[i];
+      m_lookupTable[i] = NULL;
+    }
+  }
+}
+
+void TComHash::addToTable(unsigned int hashValue, const BlockHash& blockHash)
+{
+  if (m_lookupTable[hashValue] == NULL)
+  {
+    m_lookupTable[hashValue] = new std::vector<BlockHash>;
+    m_lookupTable[hashValue]->push_back(blockHash);
+  }
+  else
+  {
+    m_lookupTable[hashValue]->push_back(blockHash);
+  }
+}
+
+int TComHash::count(unsigned int hashValue)
+{
+  if (m_lookupTable[hashValue] == NULL)
+  {
+    return 0;
+  }
+  else
+  {
+    return static_cast<int>(m_lookupTable[hashValue]->size());
+  }
+}
+
+int TComHash::count(unsigned int hashValue) const
+{
+  if (m_lookupTable[hashValue] == NULL)
+  {
+    return 0;
+  }
+  else
+  {
+    return static_cast<int>(m_lookupTable[hashValue]->size());
+  }
+}
+
+MapIterator TComHash::getFirstIterator(unsigned int hashValue)
+{
+  return m_lookupTable[hashValue]->begin();
+}
+
+const MapIterator TComHash::getFirstIterator(unsigned int hashValue) const
+{
+  return m_lookupTable[hashValue]->begin();
+}
+
+bool TComHash::hasExactMatch(unsigned int hashValue1, unsigned int hashValue2)
+{
+  if (m_lookupTable[hashValue1] == NULL)
+  {
+    return false;
+  }
+  std::vector<BlockHash>::iterator it;
+  for (it = m_lookupTable[hashValue1]->begin(); it != m_lookupTable[hashValue1]->end(); it++)
+  {
+    if ((*it).hashValue2 == hashValue2)
+    {
+      return true;
+    }
+  }
+  return false;
+}
+
+void TComHash::generateBlock2x2HashValue(const PelUnitBuf &curPicBuf, int picWidth, int picHeight, const BitDepths bitDepths, unsigned int* picBlockHash[2], bool* picBlockSameInfo[3])
+{
+  const int width = 2;
+  const int height = 2;
+  int xEnd = picWidth - width + 1;
+  int yEnd = picHeight - height + 1;
+
+  int length = width * 2;
+  bool includeChroma = false;
+  if ((curPicBuf).chromaFormat == CHROMA_444)
+  {
+    length *= 3;
+    includeChroma = true;
+  }
+  unsigned char* p = new unsigned char[length];
+
+  int pos = 0;
+  for (int yPos = 0; yPos < yEnd; yPos++)
+  {
+    for (int xPos = 0; xPos < xEnd; xPos++)
+    {
+      TComHash::getPixelsIn1DCharArrayByBlock2x2(curPicBuf, p, xPos, yPos, bitDepths, includeChroma);
+      picBlockSameInfo[0][pos] = isBlock2x2RowSameValue(p, includeChroma);
+      picBlockSameInfo[1][pos] = isBlock2x2ColSameValue(p, includeChroma);
+
+      picBlockHash[0][pos] = TComHash::getCRCValue1(p, length * sizeof(unsigned char));
+      picBlockHash[1][pos] = TComHash::getCRCValue2(p, length * sizeof(unsigned char));
+
+      pos++;
+    }
+    pos += width - 1;
+  }
+
+  delete[] p;
+}
+void TComHash::generateRectangleHashValue(int picWidth, int picHeight, int width, int height, unsigned int* srcPicBlockHash[2], unsigned int* dstPicBlockHash[2], bool* srcPicBlockSameInfo[3], bool* dstPicBlockSameInfo[3])
+{
+  //at present, only support 1:2(2:1) retangle hash value
+  CHECK(width != (height << 1) && (width << 1) != height, "Wrong")
+  bool isHorizontal = width == (height << 1) ? true : false;
+
+  int xEnd = picWidth - width + 1;
+  int yEnd = picHeight - height + 1;
+
+  int srcWidth = width >> 1;
+  int quadWidth = width >> 2;
+  int srcHeight = height >> 1;
+  int quadHeight = height >> 2;
+
+  int length = 2 * sizeof(unsigned int);
+  unsigned int* p = new unsigned int[2];
+  int pos = 0;
+  if (isHorizontal)
+  {
+    for (int yPos = 0; yPos < yEnd; yPos++)
+    {
+      for (int xPos = 0; xPos < xEnd; xPos++)
+      {
+        p[0] = srcPicBlockHash[0][pos];
+        p[1] = srcPicBlockHash[0][pos + srcWidth];
+        dstPicBlockHash[0][pos] = TComHash::getCRCValue1((unsigned char*)p, length);
+
+        p[0] = srcPicBlockHash[1][pos];
+        p[1] = srcPicBlockHash[1][pos + srcWidth];
+        dstPicBlockHash[1][pos] = TComHash::getCRCValue2((unsigned char*)p, length);
+
+        dstPicBlockSameInfo[0][pos] = srcPicBlockSameInfo[0][pos] && srcPicBlockSameInfo[0][pos + quadWidth] && srcPicBlockSameInfo[0][pos + srcWidth];
+        dstPicBlockSameInfo[1][pos] = srcPicBlockSameInfo[1][pos] && srcPicBlockSameInfo[1][pos + srcWidth];
+        pos++;
+      }
+      pos += width - 1;
+    }
+  }
+  else
+  {
+    for (int yPos = 0; yPos < yEnd; yPos++)
+    {
+      for (int xPos = 0; xPos < xEnd; xPos++)
+      {
+        p[0] = srcPicBlockHash[0][pos];
+        p[1] = srcPicBlockHash[0][pos + srcHeight * picWidth];
+        dstPicBlockHash[0][pos] = TComHash::getCRCValue1((unsigned char*)p, length);
+
+        p[0] = srcPicBlockHash[1][pos];
+        p[1] = srcPicBlockHash[1][pos + srcHeight * picWidth];
+        dstPicBlockHash[1][pos] = TComHash::getCRCValue2((unsigned char*)p, length);
+
+        dstPicBlockSameInfo[0][pos] = srcPicBlockSameInfo[0][pos] && srcPicBlockSameInfo[0][pos + srcHeight * picWidth];
+        dstPicBlockSameInfo[1][pos] = srcPicBlockSameInfo[1][pos] && srcPicBlockSameInfo[1][pos + quadHeight * picWidth] && srcPicBlockSameInfo[1][pos + srcHeight * picWidth];
+
+        pos++;
+      }
+      pos += width - 1;
+    }
+  }
+
+  int widthMinus1 = width - 1;
+  int heightMinus1 = height - 1;
+  pos = 0;
+
+  for (int yPos = 0; yPos < yEnd; yPos++)
+  {
+    for (int xPos = 0; xPos < xEnd; xPos++)
+    {
+      dstPicBlockSameInfo[2][pos] = (!dstPicBlockSameInfo[0][pos] && !dstPicBlockSameInfo[1][pos]) || (((xPos & widthMinus1) == 0) && ((yPos & heightMinus1) == 0));
+      pos++;
+    }
+    pos += width - 1;
+  }
+
+  delete[] p;
+}
+
+void TComHash::generateBlockHashValue(int picWidth, int picHeight, int width, int height, unsigned int* srcPicBlockHash[2], unsigned int* dstPicBlockHash[2], bool* srcPicBlockSameInfo[3], bool* dstPicBlockSameInfo[3])
+{
+  int xEnd = picWidth - width + 1;
+  int yEnd = picHeight - height + 1;
+
+  int srcWidth = width >> 1;
+  int quadWidth = width >> 2;
+  int srcHeight = height >> 1;
+  int quadHeight = height >> 2;
+
+  int length = 4 * sizeof(unsigned int);
+
+  unsigned int* p = new unsigned int[4];
+  int pos = 0;
+  for (int yPos = 0; yPos < yEnd; yPos++)
+  {
+    for (int xPos = 0; xPos < xEnd; xPos++)
+    {
+      p[0] = srcPicBlockHash[0][pos];
+      p[1] = srcPicBlockHash[0][pos + srcWidth];
+      p[2] = srcPicBlockHash[0][pos + srcHeight * picWidth];
+      p[3] = srcPicBlockHash[0][pos + srcHeight * picWidth + srcWidth];
+      dstPicBlockHash[0][pos] = TComHash::getCRCValue1((unsigned char*)p, length);
+
+      p[0] = srcPicBlockHash[1][pos];
+      p[1] = srcPicBlockHash[1][pos + srcWidth];
+      p[2] = srcPicBlockHash[1][pos + srcHeight * picWidth];
+      p[3] = srcPicBlockHash[1][pos + srcHeight * picWidth + srcWidth];
+      dstPicBlockHash[1][pos] = TComHash::getCRCValue2((unsigned char*)p, length);
+
+      dstPicBlockSameInfo[0][pos] = srcPicBlockSameInfo[0][pos] && srcPicBlockSameInfo[0][pos + quadWidth] && srcPicBlockSameInfo[0][pos + srcWidth]
+        && srcPicBlockSameInfo[0][pos + srcHeight * picWidth] && srcPicBlockSameInfo[0][pos + srcHeight * picWidth + quadWidth] && srcPicBlockSameInfo[0][pos + srcHeight * picWidth + srcWidth];
+
+      dstPicBlockSameInfo[1][pos] = srcPicBlockSameInfo[1][pos] && srcPicBlockSameInfo[1][pos + srcWidth] && srcPicBlockSameInfo[1][pos + quadHeight * picWidth]
+        && srcPicBlockSameInfo[1][pos + quadHeight * picWidth + srcWidth] && srcPicBlockSameInfo[1][pos + srcHeight * picWidth] && srcPicBlockSameInfo[1][pos + srcHeight * picWidth + srcWidth];
+
+      pos++;
+    }
+    pos += width - 1;
+  }
+
+  if (width >= 4)
+  {
+    int widthMinus1 = width - 1;
+    int heightMinus1 = height - 1;
+    pos = 0;
+
+    for (int yPos = 0; yPos < yEnd; yPos++)
+    {
+      for (int xPos = 0; xPos < xEnd; xPos++)
+      {
+        dstPicBlockSameInfo[2][pos] = (!dstPicBlockSameInfo[0][pos] && !dstPicBlockSameInfo[1][pos]) || (((xPos & widthMinus1) == 0) && ((yPos & heightMinus1) == 0));
+        pos++;
+      }
+      pos += width - 1;
+    }
+  }
+
+  delete[] p;
+
+}
+
+void TComHash::addToHashMapByRowWithPrecalData(unsigned int* picHash[2], bool* picIsSame, int picWidth, int picHeight, int width, int height)
+{
+  int xEnd = picWidth - width + 1;
+  int yEnd = picHeight - height + 1;
+
+  bool* srcIsAdded = picIsSame;
+  unsigned int* srcHash[2] = { picHash[0], picHash[1] };
+
+  int addValue = m_blockSizeToIndex[width][height];
+  CHECK(addValue < 0, "Wrong")
+    addValue <<= m_CRCBits;
+  int crcMask = 1 << m_CRCBits;
+  crcMask -= 1;
+
+  for (int xPos = 0; xPos < xEnd; xPos++)
+  {
+    for (int yPos = 0; yPos < yEnd; yPos++)
+    {
+      int pos = yPos * picWidth + xPos;
+      //valid data
+      if (srcIsAdded[pos])
+      {
+        BlockHash blockHash;
+        blockHash.x = xPos;
+        blockHash.y = yPos;
+
+        unsigned int      hashValue1 = (srcHash[0][pos] & crcMask) + addValue;
+        blockHash.hashValue2 = srcHash[1][pos];
+
+        addToTable(hashValue1, blockHash);
+      }
+    }
+  }
+}
+
+void TComHash::getPixelsIn1DCharArrayByBlock2x2(const PelUnitBuf &curPicBuf, unsigned char* pixelsIn1D, int xStart, int yStart, const BitDepths& bitDepths, bool includeAllComponent)
+{
+  ChromaFormat fmt = (curPicBuf).chromaFormat;
+  if (fmt != CHROMA_444)
+  {
+    includeAllComponent = false;
+  }
+
+  if (bitDepths.recon[CHANNEL_TYPE_LUMA] == 8 && bitDepths.recon[CHANNEL_TYPE_CHROMA] == 8)
+  {
+    Pel* curPel[3];
+    int stride[3];
+    for (int id = 0; id < 3; id++)
+    {
+      ComponentID compID = ComponentID(id);
+      stride[id] = (curPicBuf).get(compID).stride;
+      curPel[id] = (curPicBuf).get(compID).buf;
+      curPel[id] += (yStart >> getComponentScaleY(compID, fmt)) * stride[id] + (xStart >> getComponentScaleX(compID, fmt));
+    }
+
+    int index = 0;
+    for (int i = 0; i < 2; i++)
+    {
+      for (int j = 0; j < 2; j++)
+      {
+        pixelsIn1D[index++] = static_cast<unsigned char>(curPel[0][j]);
+        if (includeAllComponent)
+        {
+          pixelsIn1D[index++] = static_cast<unsigned char>(curPel[1][j]);
+          pixelsIn1D[index++] = static_cast<unsigned char>(curPel[2][j]);
+        }
+      }
+      curPel[0] += stride[0];
+      if (includeAllComponent)
+      {
+        curPel[1] += stride[1];
+        curPel[2] += stride[2];
+      }
+    }
+  }
+  else
+  {
+    int shift = bitDepths.recon[CHANNEL_TYPE_LUMA] - 8;
+    int shiftc = bitDepths.recon[CHANNEL_TYPE_CHROMA] - 8;
+    Pel* curPel[3];
+    int stride[3];
+    for (int id = 0; id < 3; id++)
+    {
+      ComponentID compID = ComponentID(id);
+      stride[id] = (curPicBuf).get(compID).stride;
+      curPel[id] = (curPicBuf).get(compID).buf;
+      curPel[id] += (yStart >> getComponentScaleY(compID, fmt)) * stride[id] + (xStart >> getComponentScaleX(compID, fmt));
+    }
+
+    int index = 0;
+    for (int i = 0; i < 2; i++)
+    {
+      for (int j = 0; j < 2; j++)
+      {
+        pixelsIn1D[index++] = static_cast<unsigned char>(curPel[0][j] >> shift);
+        if (includeAllComponent)
+        {
+          pixelsIn1D[index++] = static_cast<unsigned char>(curPel[1][j] >> shiftc);
+          pixelsIn1D[index++] = static_cast<unsigned char>(curPel[2][j] >> shiftc);
+        }
+      }
+      curPel[0] += stride[0];
+      if (includeAllComponent)
+      {
+        curPel[1] += stride[1];
+        curPel[2] += stride[2];
+      }
+    }
+  }
+}
+
+bool TComHash::isBlock2x2RowSameValue(unsigned char* p, bool includeAllComponent)
+{
+  if (includeAllComponent)
+  {
+    if (p[0] != p[3] || p[6] != p[9])
+    {
+      return false;
+    }
+    if (p[1] != p[4] || p[7] != p[10])
+    {
+      return false;
+    }
+    if (p[2] != p[5] || p[8] != p[11])
+    {
+      return false;
+    }
+  }
+  else
+  {
+    if (p[0] != p[1] || p[2] != p[3])
+    {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool TComHash::isBlock2x2ColSameValue(unsigned char* p, bool includeAllComponent)
+{
+  if (includeAllComponent)
+  {
+    if (p[0] != p[6] || p[3] != p[9])
+    {
+      return false;
+    }
+    if (p[1] != p[7] || p[4] != p[10])
+    {
+      return false;
+    }
+    if (p[2] != p[8] || p[5] != p[11])
+    {
+      return false;
+    }
+  }
+  else
+  {
+    if ((p[0] != p[2]) || (p[1] != p[3]))
+    {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool TComHash::getBlockHashValue(const PelUnitBuf &curPicBuf, int width, int height, int xStart, int yStart, const BitDepths bitDepths, unsigned int& hashValue1, unsigned int& hashValue2)
+{
+  int addValue = m_blockSizeToIndex[width][height];
+
+  CHECK(addValue < 0, "Wrong")
+  addValue <<= m_CRCBits;
+  int crcMask = 1 << m_CRCBits;
+  crcMask -= 1;
+  int length = 4;
+  bool includeChroma = false;
+  if ((curPicBuf).chromaFormat == CHROMA_444)
+  {
+    length *= 3;
+    includeChroma = true;
+  }
+
+  unsigned char* p = new unsigned char[length];
+  unsigned int* toHash = new unsigned int[4];
+
+  int block2x2Num = (width*height) >> 2;
+
+  unsigned int* hashValueBuffer[2][2];
+  for (int i = 0; i < 2; i++)
+  {
+    for (int j = 0; j < 2; j++)
+    {
+      hashValueBuffer[i][j] = new unsigned int[block2x2Num];
+    }
+  }
+
+  //2x2 subblock hash values in current CU
+  int subBlockInWidth = (width >> 1);
+  int subBlockInHeight = (height >> 1);
+  for (int yPos = 0; yPos < height; yPos += 2)
+  {
+    for (int xPos = 0; xPos < width; xPos += 2)
+    {
+      int pos = (yPos >> 1)*subBlockInWidth + (xPos >> 1);
+      TComHash::getPixelsIn1DCharArrayByBlock2x2(curPicBuf, p, xStart + xPos, yStart + yPos, bitDepths, includeChroma);
+
+      hashValueBuffer[0][0][pos] = TComHash::getCRCValue1(p, length * sizeof(unsigned char));
+      hashValueBuffer[1][0][pos] = TComHash::getCRCValue2(p, length * sizeof(unsigned char));
+    }
+  }
+
+  int srcSubBlockInWidth = subBlockInWidth;
+  subBlockInWidth >>= 1;
+  subBlockInHeight >>= 1;
+  length = 4 * sizeof(unsigned int);
+
+  int srcIdx = 1;
+  int dstIdx = 0;
+
+  //4x4 subblock hash values to current block hash values
+  int minSize = std::min(height, width);
+  for (int subWidth = 4; subWidth <= minSize; subWidth *= 2)
+  {
+    srcIdx = 1 - srcIdx;
+    dstIdx = 1 - dstIdx;
+
+    int dstPos = 0;
+    for (int yPos = 0; yPos < subBlockInHeight; yPos++)
+    {
+      for (int xPos = 0; xPos < subBlockInWidth; xPos++)
+      {
+        int srcPos = (yPos << 1)*srcSubBlockInWidth + (xPos << 1);
+
+        toHash[0] = hashValueBuffer[0][srcIdx][srcPos];
+        toHash[1] = hashValueBuffer[0][srcIdx][srcPos + 1];
+        toHash[2] = hashValueBuffer[0][srcIdx][srcPos + srcSubBlockInWidth];
+        toHash[3] = hashValueBuffer[0][srcIdx][srcPos + srcSubBlockInWidth + 1];
+
+        hashValueBuffer[0][dstIdx][dstPos] = TComHash::getCRCValue1((unsigned char*)toHash, length);
+
+        toHash[0] = hashValueBuffer[1][srcIdx][srcPos];
+        toHash[1] = hashValueBuffer[1][srcIdx][srcPos + 1];
+        toHash[2] = hashValueBuffer[1][srcIdx][srcPos + srcSubBlockInWidth];
+        toHash[3] = hashValueBuffer[1][srcIdx][srcPos + srcSubBlockInWidth + 1];
+        hashValueBuffer[1][dstIdx][dstPos] = TComHash::getCRCValue2((unsigned char*)toHash, length);
+
+        dstPos++;
+      }
+    }
+
+    srcSubBlockInWidth = subBlockInWidth;
+    subBlockInWidth >>= 1;
+    subBlockInHeight >>= 1;
+  }
+
+  if (width != height)//currently support 1:2 or 2:1 block size
+  {
+    CHECK(width != (height << 1) && (width << 1) != height, "Wrong")
+      bool isHorizontal = width == (height << 1) ? true : false;
+    length = 2 * sizeof(unsigned int);
+    srcIdx = 1 - srcIdx;
+    dstIdx = 1 - dstIdx;
+    if (isHorizontal)
+    {
+      toHash[0] = hashValueBuffer[0][srcIdx][0];
+      toHash[1] = hashValueBuffer[0][srcIdx][1];
+
+      hashValueBuffer[0][dstIdx][0] = TComHash::getCRCValue1((unsigned char*)toHash, length);
+
+      toHash[0] = hashValueBuffer[1][srcIdx][0];
+      toHash[1] = hashValueBuffer[1][srcIdx][1];
+      hashValueBuffer[1][dstIdx][0] = TComHash::getCRCValue2((unsigned char*)toHash, length);
+    }
+    else
+    {
+      CHECK(srcSubBlockInWidth != 1, "Wrong")
+      toHash[0] = hashValueBuffer[0][srcIdx][0];
+      toHash[1] = hashValueBuffer[0][srcIdx][srcSubBlockInWidth];
+
+      hashValueBuffer[0][dstIdx][0] = TComHash::getCRCValue1((unsigned char*)toHash, length);
+
+      toHash[0] = hashValueBuffer[1][srcIdx][0];
+      toHash[1] = hashValueBuffer[1][srcIdx][srcSubBlockInWidth];
+      hashValueBuffer[1][dstIdx][0] = TComHash::getCRCValue2((unsigned char*)toHash, length);
+    }
+  }
+
+  hashValue1 = (hashValueBuffer[0][dstIdx][0] & crcMask) + addValue;
+  hashValue2 = hashValueBuffer[1][dstIdx][0];
+
+  delete[] toHash;
+
+  for (int i = 0; i < 2; i++)
+  {
+    for (int j = 0; j < 2; j++)
+    {
+      delete[] hashValueBuffer[i][j];
+    }
+  }
+
+  delete[] p;
+
+  return true;
+}
+
+void TComHash::initBlockSizeToIndex()
+{
+  for (int i = 0; i < 65; i++)
+  {
+    for (int j = 0; j < 65; j++)
+    {
+      m_blockSizeToIndex[i][j] = -1;
+    }
+  }
+
+  m_blockSizeToIndex[8][8] = 0;
+  m_blockSizeToIndex[16][16] = 1;
+  m_blockSizeToIndex[32][32] = 2;
+  m_blockSizeToIndex[64][64] = 3;
+  m_blockSizeToIndex[4][4] = 4;
+  m_blockSizeToIndex[4][8] = 5;
+  m_blockSizeToIndex[8][4] = 6;
+}
+
+unsigned int TComHash::getCRCValue1(unsigned char* p, int length)
+{
+  m_crcCalculator1.reset();
+  m_crcCalculator1.processData(p, length);
+  return m_crcCalculator1.getCRC();
+}
+
+unsigned int TComHash::getCRCValue2(unsigned char* p, int length)
+{
+  m_crcCalculator2.reset();
+  m_crcCalculator2.processData(p, length);
+  return m_crcCalculator2.getCRC();
+}
+//! \}
diff --git a/source/Lib/CommonLib/Hash.h b/source/Lib/CommonLib/Hash.h
new file mode 100644
index 0000000000000000000000000000000000000000..f47f14469f04ef6efd2a47b2eb5d0df5ad290e05
--- /dev/null
+++ b/source/Lib/CommonLib/Hash.h
@@ -0,0 +1,134 @@
+/* The copyright in this software is being made available under the BSD
+ * License, included below. This software may be subject to other third party
+ * and contributor rights, including patent rights, and no such rights are
+ * granted under this license.
+ *
+ * Copyright (c) 2010-2019, ITU/ISO/IEC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+ *    be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /** \file     Hash.h
+     \brief    Hash class (header)
+ */
+
+
+#ifndef __HASH__
+#define __HASH__
+
+ // Include files
+
+#include "CommonLib/Buffer.h"
+#include "CommonLib/CommonDef.h"
+#include "CommonLib/TrQuant.h"
+#include "CommonLib/Unit.h"
+#include "CommonLib/UnitPartitioner.h"
+#include <vector>
+
+
+struct BlockHash
+{
+  short x;
+  short y;
+  unsigned int hashValue2;
+};
+
+typedef std::vector<BlockHash>::iterator MapIterator;
+
+// ====================================================================================================================
+// Class definitions
+// ====================================================================================================================
+
+
+struct TCRCCalculatorLight
+{
+public:
+  TCRCCalculatorLight(unsigned int bits, unsigned int truncPoly);
+  ~TCRCCalculatorLight();
+
+public:
+  void processData(unsigned char* curData, unsigned int dataLength);
+  void reset() { m_remainder = 0; }
+  unsigned int getCRC() { return m_remainder & m_finalResultMask; }
+
+private:
+  void xInitTable();
+
+private:
+  unsigned int m_remainder;
+  unsigned int m_truncPoly;
+  unsigned int m_bits;
+  unsigned int m_table[256];
+  unsigned int m_finalResultMask;
+};
+
+
+struct TComHash
+{
+public:
+  TComHash();
+  ~TComHash();
+  void create();
+  void clearAll();
+  void addToTable(unsigned int hashValue, const BlockHash& blockHash);
+  int count(unsigned int hashValue);
+  int count(unsigned int hashValue) const;
+  MapIterator getFirstIterator(unsigned int hashValue);
+  const MapIterator getFirstIterator(unsigned int hashValue) const;
+  bool hasExactMatch(unsigned int hashValue1, unsigned int hashValue2);
+
+  void generateBlock2x2HashValue(const PelUnitBuf &curPicBuf, int picWidth, int picHeight, const BitDepths bitDepths, unsigned int* picBlockHash[2], bool* picBlockSameInfo[3]);
+  void generateBlockHashValue(int picWidth, int picHeight, int width, int height, unsigned int* srcPicBlockHash[2], unsigned int* dstPicBlockHash[2], bool* srcPicBlockSameInfo[3], bool* dstPicBlockSameInfo[3]);
+  void generateRectangleHashValue(int picWidth, int picHeight, int width, int height, unsigned int* srcPicBlockHash[2], unsigned int* dstPicBlockHash[2], bool* srcPicBlockSameInfo[3], bool* dstPicBlockSameInfo[3]);
+  void addToHashMapByRowWithPrecalData(unsigned int* srcHash[2], bool* srcIsSame, int picWidth, int picHeight, int width, int height);
+  bool isInitial() { return tableHasContent; }
+  void setInitial() { tableHasContent = true; }
+
+
+
+public:
+  static unsigned int   getCRCValue1(unsigned char* p, int length);
+  static unsigned int   getCRCValue2(unsigned char* p, int length);
+  static void getPixelsIn1DCharArrayByBlock2x2(const PelUnitBuf &curPicBuf, unsigned char* pixelsIn1D, int xStart, int yStart, const BitDepths& bitDepths, bool includeAllComponent = true);
+  static bool isBlock2x2RowSameValue(unsigned char* p, bool includeAllComponent = true);
+  static bool isBlock2x2ColSameValue(unsigned char* p, bool includeAllComponent = true);
+  static bool getBlockHashValue(const PelUnitBuf &curPicBuf, int width, int height, int xStart, int yStart, const BitDepths bitDepths, unsigned int& hashValue1, unsigned int& hashValue2);
+  static void initBlockSizeToIndex();
+
+private:
+  std::vector<BlockHash>** m_lookupTable;
+  bool tableHasContent;
+
+private:
+  static const int m_CRCBits = 16;
+  static const int m_blockSizeBits = 3;
+  static int m_blockSizeToIndex[65][65];
+
+  static TCRCCalculatorLight m_crcCalculator1;
+  static TCRCCalculatorLight m_crcCalculator2;
+};
+
+#endif // __HASH__
diff --git a/source/Lib/CommonLib/Picture.cpp b/source/Lib/CommonLib/Picture.cpp
index d319f1f7ed99cae3d0b589db280489359e00733d..aed517f64659a82a65186d61b243c79ff5ecd93c 100644
--- a/source/Lib/CommonLib/Picture.cpp
+++ b/source/Lib/CommonLib/Picture.cpp
@@ -747,6 +747,9 @@ void Picture::create(const ChromaFormat &_chromaFormat, const Size &size, const
 
   m_ctuArea = UnitArea( _chromaFormat, Area( Position{ 0, 0 }, Size( _maxCUSize, _maxCUSize ) ) );
 #endif
+#if JVET_M0253_HASH_ME
+  m_hashMap.clearAll();
+#endif
 }
 
 void Picture::destroy()
@@ -762,7 +765,9 @@ void Picture::destroy()
   {
     M_BUFS( jId, t ).destroy();
   }
-
+#if JVET_M0253_HASH_ME
+  m_hashMap.clearAll();
+#endif
   if( cs )
   {
     cs->destroy();
@@ -1162,3 +1167,64 @@ bool Picture::getSpliceFull()
     return false;
   return true;
 }
+
+#if JVET_M0253_HASH_ME
+void Picture::addPictureToHashMapForInter()
+{
+  int picWidth = slices[0]->getSPS()->getPicWidthInLumaSamples();
+  int picHeight = slices[0]->getSPS()->getPicHeightInLumaSamples();
+  unsigned int* blockHashValues[2][2];
+  bool* bIsBlockSame[2][3];
+
+  for (int i = 0; i < 2; i++)
+  {
+    for (int j = 0; j < 2; j++)
+    {
+      blockHashValues[i][j] = new unsigned int[picWidth*picHeight];
+    }
+
+    for (int j = 0; j < 3; j++)
+    {
+      bIsBlockSame[i][j] = new bool[picWidth*picHeight];
+    }
+  }
+
+  m_hashMap.create();
+  m_hashMap.generateBlock2x2HashValue(getOrigBuf(), picWidth, picHeight, slices[0]->getSPS()->getBitDepths(), blockHashValues[0], bIsBlockSame[0]);//2x2
+  m_hashMap.generateBlockHashValue(picWidth, picHeight, 4, 4, blockHashValues[0], blockHashValues[1], bIsBlockSame[0], bIsBlockSame[1]);//4x4
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[1], bIsBlockSame[1][2], picWidth, picHeight, 4, 4);
+
+  m_hashMap.generateRectangleHashValue(picWidth, picHeight, 8, 4, blockHashValues[1], blockHashValues[0], bIsBlockSame[1], bIsBlockSame[0]);//8x4
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[0], bIsBlockSame[0][2], picWidth, picHeight, 8, 4);
+
+  m_hashMap.generateRectangleHashValue(picWidth, picHeight, 4, 8, blockHashValues[1], blockHashValues[0], bIsBlockSame[1], bIsBlockSame[0]);//4x8
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[0], bIsBlockSame[0][2], picWidth, picHeight, 4, 8);
+
+  m_hashMap.generateBlockHashValue(picWidth, picHeight, 8, 8, blockHashValues[1], blockHashValues[0], bIsBlockSame[1], bIsBlockSame[0]);//8x8
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[0], bIsBlockSame[0][2], picWidth, picHeight, 8, 8);
+
+  m_hashMap.generateBlockHashValue(picWidth, picHeight, 16, 16, blockHashValues[0], blockHashValues[1], bIsBlockSame[0], bIsBlockSame[1]);//16x16
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[1], bIsBlockSame[1][2], picWidth, picHeight, 16, 16);
+
+  m_hashMap.generateBlockHashValue(picWidth, picHeight, 32, 32, blockHashValues[1], blockHashValues[0], bIsBlockSame[1], bIsBlockSame[0]);//32x32
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[0], bIsBlockSame[0][2], picWidth, picHeight, 32, 32);
+
+  m_hashMap.generateBlockHashValue(picWidth, picHeight, 64, 64, blockHashValues[0], blockHashValues[1], bIsBlockSame[0], bIsBlockSame[1]);//64x64
+  m_hashMap.addToHashMapByRowWithPrecalData(blockHashValues[1], bIsBlockSame[1][2], picWidth, picHeight, 64, 64);
+
+  m_hashMap.setInitial();
+
+  for (int i = 0; i < 2; i++)
+  {
+    for (int j = 0; j < 2; j++)
+    {
+      delete[] blockHashValues[i][j];
+    }
+
+    for (int j = 0; j < 3; j++)
+    {
+      delete[] bIsBlockSame[i][j];
+    }
+  }
+}
+#endif
diff --git a/source/Lib/CommonLib/Picture.h b/source/Lib/CommonLib/Picture.h
index 2e90cc5382746f8a7675372eceb892748202fa98..1b6797720ae5d2ab1dc5d9de55ce2d42c1d7d369 100644
--- a/source/Lib/CommonLib/Picture.h
+++ b/source/Lib/CommonLib/Picture.h
@@ -46,7 +46,9 @@
 #include "Unit.h"
 #include "Slice.h"
 #include "CodingStructure.h"
-
+#if JVET_M0253_HASH_ME
+#include "Hash.h"
+#endif
 #include <deque>
 
 #if ENABLE_WPP_PARALLELISM || ENABLE_SPLIT_PARALLELISM
@@ -259,6 +261,13 @@ public:
   PelStorage m_bufs[NUM_PIC_TYPES];
 #endif
 
+#if JVET_M0253_HASH_ME
+  TComHash           m_hashMap;
+  TComHash*          getHashMap() { return &m_hashMap; }
+  const TComHash*    getHashMap() const { return &m_hashMap; }
+  void               addPictureToHashMapForInter();
+#endif
+
   CodingStructure*   cs;
   std::deque<Slice*> slices;
   SEIMessages        SEIs;
diff --git a/source/Lib/CommonLib/Slice.cpp b/source/Lib/CommonLib/Slice.cpp
index fe1541e008a14867e3b0faad4cff4c47f894fd1f..218c6b78b0b491e7beb269407aa3718bf0d1f6d5 100644
--- a/source/Lib/CommonLib/Slice.cpp
+++ b/source/Lib/CommonLib/Slice.cpp
@@ -695,6 +695,9 @@ void Slice::decodingRefreshMarking(int& pocCRA, bool& bRefreshPending, PicList&
       if (rpcPic->getPOC() != pocCurr)
       {
         rpcPic->referenced = false;
+#if JVET_M0253_HASH_ME
+        rpcPic->getHashMap()->clearAll();
+#endif
       }
       iterPic++;
     }
@@ -722,6 +725,9 @@ void Slice::decodingRefreshMarking(int& pocCRA, bool& bRefreshPending, PicList&
           if (rpcPic->getPOC() != pocCurr && rpcPic->getPOC() != m_iLastIDR)
           {
             rpcPic->referenced = false;
+#if JVET_M0253_HASH_ME
+            rpcPic->getHashMap()->clearAll();
+#endif
           }
           iterPic++;
         }
@@ -739,6 +745,9 @@ void Slice::decodingRefreshMarking(int& pocCRA, bool& bRefreshPending, PicList&
           if (rpcPic->getPOC() != pocCurr && rpcPic->getPOC() != pocCRA)
           {
             rpcPic->referenced = false;
+#if JVET_M0253_HASH_ME
+            rpcPic->getHashMap()->clearAll();
+#endif
           }
           iterPic++;
         }
@@ -1150,6 +1159,9 @@ void Slice::applyReferencePictureSet( PicList& rcListPic, const ReferencePicture
       pcPic->referenced = false;
       pcPic->usedByCurr = false;
       pcPic->longTerm   = false;
+#if JVET_M0253_HASH_ME
+      pcPic->getHashMap()->clearAll();
+#endif
     }
 
     // sanity checks
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index cb1dc56721259efdd479c56284673317f95b2bfc..ba16df39f1f33060176be94dd1b4b52196f726ed 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -53,6 +53,8 @@
 #define JVET_M0471_LONG_DEBLOCKING_FILTERS                1 
 #define JVET_M0470                                        1 // Fixed GR/TU+EG-k transition point, use limited prefix length for escape codes
 
+#define JVET_M0253_HASH_ME                                1
+
 #define JVET_M0257                                        1 // Scan only non zero-out regions of large TUs
 #define JVET_M0193_PAIR_AVG_REDUCTION                     1 //Use only one pairwise average candidate
 
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index bbfd74821a63dea93ca14b8dec8a7450abc75930..d61e6dd6ffe60b7a205888dbfbd1b59400b9e6c0 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -243,6 +243,8 @@ protected:
 #endif
 #if JVET_M0246_AFFINE_AMVR
   bool      m_AffineAmvr;
+#endif#if JVET_M0253_HASH_ME
+bool      m_HashME;
 #endif
 #if JVET_M0247_AFFINE_AMVR_ENCOPT
   bool      m_AffineAmvrEncOpt;
@@ -766,6 +768,10 @@ public:
   void      setAllowDisFracMMVD             ( bool b )       { m_allowDisFracMMVD = b;    }
   bool      getAllowDisFracMMVD             ()         const { return m_allowDisFracMMVD; }
 #endif
+#if JVET_M0253_HASH_ME
+  void      setUseHashME                    ( bool b )       { m_HashME = b; }
+  bool      getUseHashME                    ()         const { return m_HashME; }
+#endif
 #if JVET_M0246_AFFINE_AMVR
   void      setUseAffineAmvr                ( bool b )       { m_AffineAmvr = b;    }
   bool      getUseAffineAmvr                ()         const { return m_AffineAmvr; }
diff --git a/source/Lib/EncoderLib/EncCu.cpp b/source/Lib/EncoderLib/EncCu.cpp
index 32639339932f9997b2656b341fd10f747d5831a7..8b2d43ec02b6ce81dae3087976e3a577d6890b77 100644
--- a/source/Lib/EncoderLib/EncCu.cpp
+++ b/source/Lib/EncoderLib/EncCu.cpp
@@ -754,6 +754,12 @@ void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Par
       }
 
     }
+#if JVET_M0253_HASH_ME
+    else if (currTestMode.type == ETM_HASH_INTER)
+    {
+      xCheckRDCostHashInter( tempCS, bestCS, partitioner, currTestMode );
+    }
+#endif
     else if( currTestMode.type == ETM_AFFINE )
     {
       xCheckRDCostAffineMerge2Nx2N( tempCS, bestCS, partitioner, currTestMode );
@@ -1721,7 +1727,56 @@ void EncCu::xFillPCMBuffer( CodingUnit &cu )
     }
   }
 }
+#if JVET_M0253_HASH_ME
+void EncCu::xCheckRDCostHashInter( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
+{
+  bool isPerfectMatch = false;
+
+  tempCS->initStructData(encTestMode.qp, encTestMode.lossless);
+  m_pcInterSearch->resetBufferedUniMotions();
+  m_pcInterSearch->setAffineModeSelected(false);
+  CodingUnit &cu = tempCS->addCU(tempCS->area, partitioner.chType);
+
+  partitioner.setCUData(cu);
+  cu.slice = tempCS->slice;
+  cu.skip = false;
+  cu.predMode = MODE_INTER;
+  cu.transQuantBypass = encTestMode.lossless;
+  cu.chromaQpAdj = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
+  cu.qp = encTestMode.qp;
+  cu.ibc = false;
+  CU::addPUs(cu);
+  cu.mmvdSkip = false;
+  cu.firstPU->mmvdMergeFlag = false;
+
+  if (m_pcInterSearch->predInterHashSearch(cu, partitioner, isPerfectMatch))
+  {
+    const unsigned wIdx = gp_sizeIdxInfo->idxFrom(tempCS->area.lwidth());
+    double equGBiCost = MAX_DOUBLE;
 
+#if JVET_M0464_UNI_MTS
+    xEncodeInterResidual(tempCS, bestCS, partitioner, encTestMode, 0
+      , m_pImvTempCS ? m_pImvTempCS[wIdx] : NULL
+      , 0
+      , &equGBiCost
+#else
+    xEncodeInterResidual(tempCS, bestCS, partitioner, encTestMode, 0
+      , m_pImvTempCS ? m_pImvTempCS[wIdx] : NULL
+      , 1
+      , 0
+      , &equGBiCost
+#endif
+    );
+  }
+  tempCS->initStructData(encTestMode.qp, encTestMode.lossless);
+
+  if (cu.lwidth() != 64)
+  {
+    isPerfectMatch = false;
+  }
+  m_modeCtrl->setIsHashPerfectMatch(isPerfectMatch);
+}
+#endif
 
 void EncCu::xCheckRDCostMerge2Nx2N( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
 {
@@ -2148,10 +2203,25 @@ void EncCu::xCheckRDCostMerge2Nx2N( CodingStructure *&tempCS, CodingStructure *&
     }
   }
 
+#if !JVET_M0253_HASH_ME
   const uint32_t iteration = encTestMode.lossless ? 1 : 2;
 
   // 2. Pass: check candidates using full RD test
   for( uint32_t uiNoResidualPass = 0; uiNoResidualPass < iteration; uiNoResidualPass++ )
+#else
+  uint32_t iteration;
+  uint32_t iterationBegin = m_modeCtrl->getIsHashPerfectMatch() ? 1 : 0;
+  if (encTestMode.lossless)
+  {
+    iteration = 1;
+    iterationBegin = 0;
+  }
+  else
+  {
+    iteration = 2;
+  }
+  for (uint32_t uiNoResidualPass = iterationBegin; uiNoResidualPass < iteration; ++uiNoResidualPass)
+#endif
   {
     for( uint32_t uiMrgHADIdx = 0; uiMrgHADIdx < uiNumMrgSATDCand; uiMrgHADIdx++ )
     {
@@ -2514,8 +2584,23 @@ void EncCu::xCheckRDCostMergeTriangle2Nx2N( CodingStructure *&tempCS, CodingStru
   }
 
   {
+#if !JVET_M0253_HASH_ME
     const uint8_t iteration = encTestMode.lossless ? 1 : 2;
     for( uint8_t noResidualPass = 0; noResidualPass < iteration; noResidualPass++ )
+#else
+    uint8_t iteration;
+    uint8_t iterationBegin = m_modeCtrl->getIsHashPerfectMatch() ? 1 : 0;
+    if (encTestMode.lossless)
+    {
+      iteration = 1;
+      iterationBegin = 0;
+    }
+    else
+    {
+      iteration = 2;
+    }
+    for (uint8_t noResidualPass = iterationBegin; noResidualPass < iteration; ++noResidualPass)
+#endif
     {
       for( uint8_t mrgHADIdx = 0; mrgHADIdx < triangleNumMrgSATDCand; mrgHADIdx++ )
       {
@@ -2774,10 +2859,25 @@ void EncCu::xCheckRDCostAffineMerge2Nx2N( CodingStructure *&tempCS, CodingStruct
     }
   }
 
+#if !JVET_M0253_HASH_ME
   const uint32_t iteration = encTestMode.lossless ? 1 : 2;
 
   // 2. Pass: check candidates using full RD test
   for ( uint32_t uiNoResidualPass = 0; uiNoResidualPass < iteration; uiNoResidualPass++ )
+#else
+  uint32_t iteration;
+  uint32_t iterationBegin = m_modeCtrl->getIsHashPerfectMatch() ? 1 : 0;
+  if (encTestMode.lossless)
+  {
+    iteration = 1;
+    iterationBegin = 0;
+  }
+  else
+  {
+    iteration = 2;
+  }
+  for (uint32_t uiNoResidualPass = iterationBegin; uiNoResidualPass < iteration; ++uiNoResidualPass)
+#endif
   {
     for ( uint32_t uiMrgHADIdx = 0; uiMrgHADIdx < uiNumMrgSATDCand; uiMrgHADIdx++ )
     {
diff --git a/source/Lib/EncoderLib/EncCu.h b/source/Lib/EncoderLib/EncCu.h
index 920e0eca595016e272842c99c0eb4cf9f29e72dd..a700255dc5cd032c0c45d355d22e9f573a23d081 100644
--- a/source/Lib/EncoderLib/EncCu.h
+++ b/source/Lib/EncoderLib/EncCu.h
@@ -210,6 +210,9 @@ protected:
   void xCheckDQP              ( CodingStructure& cs, Partitioner& partitioner, bool bKeepCtx = false);
   void xFillPCMBuffer         ( CodingUnit &cu);
 
+#if JVET_M0253_HASH_ME
+  void xCheckRDCostHashInter  ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode );
+#endif
   void xCheckRDCostAffineMerge2Nx2N
                               ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode );
   void xCheckRDCostInter      ( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &pm, const EncTestMode& encTestMode );
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index bc863bb6c78e3f4f93fcdc40f4ef2558c89ccb71..62866d4e686312cb5e2db017977bca34fadfdffe 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -1643,6 +1643,75 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic,
     //  Set reference list
     pcSlice->setRefPicList ( rcListPic );
 
+#if JVET_M0253_HASH_ME
+    if (m_pcCfg->getUseHashME())
+    {
+      PicList::iterator iterPic = rcListPic.begin();
+      while (iterPic != rcListPic.end())
+      {
+        Picture* refPic = *(iterPic++);
+
+        if (refPic->poc != pcPic->poc && refPic->referenced)
+        {
+          if (!refPic->getHashMap()->isInitial())
+          {
+            if (refPic->getPOC() == 0)
+            {
+              Pel* picSrc = refPic->getOrigBuf().get(COMPONENT_Y).buf;
+              int stridePic = refPic->getOrigBuf().get(COMPONENT_Y).stride;
+              int picWidth = pcSlice->getSPS()->getPicWidthInLumaSamples();
+              int picHeight = pcSlice->getSPS()->getPicHeightInLumaSamples();
+              int blockSize = 4;
+              int allNum = 0;
+              int simpleNum = 0;
+              for (int j = 0; j <= picHeight - blockSize; j += blockSize)
+              {
+                for (int i = 0; i <= picWidth - blockSize; i += blockSize)
+                {
+                  Pel* curBlock = picSrc + j * stridePic + i;
+                  bool isHorSame = true;
+                  for (int m = 0; m < blockSize&&isHorSame; m++)
+                  {
+                    for (int n = 1; n < blockSize&&isHorSame; n++)
+                    {
+                      if (curBlock[m*stridePic] != curBlock[m*stridePic + n])
+                      {
+                        isHorSame = false;
+                      }
+                    }
+                  }
+                  bool isVerSame = true;
+                  for (int m = 1; m < blockSize&&isVerSame; m++)
+                  {
+                    for (int n = 0; n < blockSize&&isVerSame; n++)
+                    {
+                      if (curBlock[n] != curBlock[m*stridePic + n])
+                      {
+                        isVerSame = false;
+                      }
+                    }
+                  }
+                  allNum++;
+                  if (isHorSame || isVerSame)
+                  {
+                    simpleNum++;
+                  }
+                }
+              }
+
+              if (simpleNum < 0.3*allNum)
+              {
+                m_pcCfg->setUseHashME(false);
+                break;
+              }
+            }
+            refPic->addPictureToHashMapForInter();
+          }
+        }
+      }
+
+    }
+#endif
     if( m_pcCfg->getUseAMaxBT() )
     {
       if( !pcSlice->isIRAP() )
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index 4d8506f0217d3292f6c752caf591887c6d7e76a7..4176ced62d04ec2191e9deae1a3b5aca194dae38 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -84,9 +84,9 @@ void EncLib::create ()
 {
   // initialize global variables
   initROM();
-
-
-
+#if JVET_M0253_HASH_ME
+  TComHash::initBlockSizeToIndex();
+#endif
   m_iPOCLast = m_compositeRefEnabled ? -2 : -1;
   // create processing unit classes
   m_cGOPEncoder.        create( );
@@ -756,6 +756,9 @@ void EncLib::xGetNewPicBuffer ( std::list<PelUnitBuf*>& rcListPicYuvRecOut, Pict
   rpcPic->setBorderExtension( false );
   rpcPic->reconstructed = false;
   rpcPic->referenced = true;
+#if JVET_M0253_HASH_ME
+  rpcPic->getHashMap()->clearAll();
+#endif
 
   m_iPOCLast += (m_compositeRefEnabled ? 2 : 1);
   m_iNumPicRcvd++;
diff --git a/source/Lib/EncoderLib/EncModeCtrl.cpp b/source/Lib/EncoderLib/EncModeCtrl.cpp
index ecd823301016b267b8f0fe099da56a9820289c67..560a59cf5cb805694c73e311f5cbabb52ee2baeb 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.cpp
+++ b/source/Lib/EncoderLib/EncModeCtrl.cpp
@@ -1183,6 +1183,15 @@ void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStru
           m_ComprCUCtxList.back().testModes.push_back( { ETM_AFFINE,    ETO_STANDARD, qp, lossless } );
         }
       }
+#if JVET_M0253_HASH_ME
+      if (m_pcEncCfg->getUseHashME())
+      {
+        if ((cs.area.lwidth() == cs.area.lheight() && cs.area.lwidth() <= 64 && cs.area.lwidth() >= 4) || (cs.area.lwidth() == 4 && cs.area.lheight() == 8) || (cs.area.lwidth() == 8 && cs.area.lheight() == 4))
+        {
+          m_ComprCUCtxList.back().testModes.push_back({ ETM_HASH_INTER, ETO_STANDARD, qp, lossless });
+        }
+      }
+#endif
     }
   }
 
@@ -1206,6 +1215,12 @@ bool EncModeCtrlMTnoRQT::tryMode( const EncTestMode& encTestmode, const CodingSt
   ComprCUCtx& cuECtx = m_ComprCUCtxList.back();
 
   // Fast checks, partitioning depended
+#if JVET_M0253_HASH_ME
+  if (cuECtx.isHashPerfectMatch && encTestmode.type != ETM_MERGE_SKIP && encTestmode.type != ETM_AFFINE && encTestmode.type != ETM_MERGE_TRIANGLE)
+  {
+    return false;
+  }
+#endif
 
   // if early skip detected, skip all modes checking but the splits
   if( cuECtx.earlySkip && m_pcEncCfg->getUseEarlySkipDetection() && !isModeSplit( encTestmode ) && !( isModeInter( encTestmode ) ) )
diff --git a/source/Lib/EncoderLib/EncModeCtrl.h b/source/Lib/EncoderLib/EncModeCtrl.h
index 422e4c80840c1bd6b3960c1b1abe75129e6c0249..b44f18c7d9a35d84d4cec94f83c7c85b4ddb6f8f 100644
--- a/source/Lib/EncoderLib/EncModeCtrl.h
+++ b/source/Lib/EncoderLib/EncModeCtrl.h
@@ -54,6 +54,9 @@
 
 enum EncTestModeType
 {
+#if JVET_M0253_HASH_ME
+  ETM_HASH_INTER,
+#endif
   ETM_MERGE_SKIP,
   ETM_INTER_ME,
   ETM_AFFINE,
@@ -137,6 +140,9 @@ inline bool isModeInter( const EncTestMode& encTestmode ) // perhaps remove
           || encTestmode.type == ETM_MERGE_SKIP
           || encTestmode.type == ETM_AFFINE
           || encTestmode.type == ETM_MERGE_TRIANGLE
+#if JVET_M0253_HASH_ME
+          || encTestmode.type == ETM_HASH_INTER
+#endif
          );
 }
 
@@ -178,6 +184,10 @@ struct ComprCUCtx
     , testModes     (            )
     , lastTestMode  (            )
     , earlySkip     ( false      )
+#if JVET_M0253_HASH_ME
+    , isHashPerfectMatch
+                    ( false      )
+#endif
     , bestCS        ( nullptr    )
     , bestCU        ( nullptr    )
     , bestTU        ( nullptr    )
@@ -212,6 +222,9 @@ struct ComprCUCtx
   std::vector<EncTestMode>          testModes;
   EncTestMode                       lastTestMode;
   bool                              earlySkip;
+#if JVET_M0253_HASH_ME
+  bool                              isHashPerfectMatch;
+#endif
   CodingStructure                  *bestCS;
   CodingUnit                       *bestCU;
   TransformUnit                    *bestTU;
@@ -287,6 +300,10 @@ public:
   EncTestMode  currTestMode         () const;
   EncTestMode  lastTestMode         () const;
   void         setEarlySkipDetected ();
+#if JVET_M0253_HASH_ME
+  void         setIsHashPerfectMatch( bool b ) { m_ComprCUCtxList.back().isHashPerfectMatch = b; }
+  bool         getIsHashPerfectMatch() { return m_ComprCUCtxList.back().isHashPerfectMatch; }
+#endif
   virtual void setBest              ( CodingStructure& cs );
   bool         anyMode              () const;
 
diff --git a/source/Lib/EncoderLib/InterSearch.cpp b/source/Lib/EncoderLib/InterSearch.cpp
index 65d35faf49c6580fbe3c340ae2ce1b958e9ae45e..814e65b01a56d07be0eea1f012548819e6f02f45 100644
--- a/source/Lib/EncoderLib/InterSearch.cpp
+++ b/source/Lib/EncoderLib/InterSearch.cpp
@@ -697,6 +697,12 @@ Distortion InterSearch::xPatternRefinement( const CPelBuf* pcPatternKey,
   const Mv* pcMvRefine = (iFrac == 2 ? s_acMvRefineH : s_acMvRefineQ);
   for (uint32_t i = 0; i < 9; i++)
   {
+#if JVET_M0253_HASH_ME
+    if (m_bSkipFracME && i > 0)
+    {
+      break;
+    }
+#endif
     Mv cMvTest = pcMvRefine[i];
     cMvTest += baseRefMv;
 
@@ -1538,6 +1544,337 @@ void InterSearch::xxIBCHashSearch(PredictionUnit& pu, Mv* mvPred, int numMvPred,
 }
 
 
+#if JVET_M0253_HASH_ME
+void InterSearch::addToSortList(std::list<BlockHash>& listBlockHash, std::list<int>& listCost, int cost, const BlockHash& blockHash)
+{
+  std::list<BlockHash>::iterator itBlockHash = listBlockHash.begin();
+  std::list<int>::iterator itCost = listCost.begin();
+
+  while (itCost != listCost.end())
+  {
+    if (cost < (*itCost))
+    {
+      listCost.insert(itCost, cost);
+      listBlockHash.insert(itBlockHash, blockHash);
+      return;
+    }
+
+    ++itCost;
+    ++itBlockHash;
+  }
+
+  listCost.push_back(cost);
+  listBlockHash.push_back(blockHash);
+}
+
+void InterSearch::selectMatchesInter(const MapIterator& itBegin, int count, std::list<BlockHash>& listBlockHash, const BlockHash& currBlockHash)
+{
+  const int maxReturnNumber = 5;
+
+  listBlockHash.clear();
+  std::list<int> listCost;
+  listCost.clear();
+
+  MapIterator it = itBegin;
+  for (int i = 0; i < count; i++, it++)
+  {
+    if ((*it).hashValue2 != currBlockHash.hashValue2)
+    {
+      continue;
+    }
+
+    int currCost = RdCost::xGetExpGolombNumberOfBits((*it).x - currBlockHash.x) +
+      RdCost::xGetExpGolombNumberOfBits((*it).y - currBlockHash.y);
+
+    if (listBlockHash.size() < maxReturnNumber)
+    {
+      addToSortList(listBlockHash, listCost, currCost, (*it));
+    }
+    else if (!listCost.empty() && currCost < listCost.back())
+    {
+      listCost.pop_back();
+      listBlockHash.pop_back();
+      addToSortList(listBlockHash, listCost, currCost, (*it));
+    }
+  }
+}
+
+Distortion InterSearch::getSAD(const Pel* refPel, const int refStride, const Pel* curPel, const int curStride, int width, int height, const BitDepths& bitDepths)
+{
+  Distortion dist = 0;
+
+  for (int i = 0; i < height; i++)
+  {
+    for (int j = 0; j < width; j++)
+    {
+      dist += abs(refPel[j] - curPel[j]);
+    }
+    refPel += refStride;
+    curPel += curStride;
+  }
+
+  if (bitDepths.recon[CHANNEL_TYPE_LUMA] == 8)
+  {
+    return dist;
+  }
+  else
+  {
+    int shift = DISTORTION_PRECISION_ADJUSTMENT(bitDepths.recon[CHANNEL_TYPE_LUMA]);
+    return dist >> shift;
+  }
+  return 0;
+}
+
+int InterSearch::xHashInterPredME(const PredictionUnit& pu, RefPicList currRefPicList, int currRefPicIndex, Mv bestMv[5])
+{
+  int width = pu.cu->lumaSize().width;
+  int height = pu.cu->lumaSize().height;
+  int xPos = pu.cu->lumaPos().x;
+  int yPos = pu.cu->lumaPos().y;
+
+  unsigned int hashValue1;
+  unsigned int hashValue2;
+
+  if (!TComHash::getBlockHashValue((pu.cs->picture->getOrigBuf()), width, height, xPos, yPos, pu.cu->slice->getSPS()->getBitDepths(), hashValue1, hashValue2))
+  {
+    return 0;
+  }
+  BlockHash currBlockHash;
+  currBlockHash.x = xPos;
+  currBlockHash.y = yPos;
+  currBlockHash.hashValue2 = hashValue2;
+
+  int count = static_cast<int>(pu.cu->slice->getRefPic(currRefPicList, currRefPicIndex)->getHashMap()->count(hashValue1));
+  if (count == 0)
+  {
+    return 0;
+  }
+
+  list<BlockHash> listBlockHash;
+  selectMatchesInter(pu.cu->slice->getRefPic(currRefPicList, currRefPicIndex)->getHashMap()->getFirstIterator(hashValue1), count, listBlockHash, currBlockHash);
+
+  if (listBlockHash.empty())
+  {
+    return 0;
+  }
+
+  int totalSize = 0;
+  list<BlockHash>::iterator it = listBlockHash.begin();
+  for (int i = 0; i < 5 && i < listBlockHash.size(); i++, it++)
+  {
+    bestMv[i].set((*it).x - currBlockHash.x, (*it).y - currBlockHash.y);
+    totalSize++;
+  }
+
+  return totalSize;
+}
+
+bool InterSearch::xHashInterEstimation(PredictionUnit& pu, RefPicList& bestRefPicList, int& bestRefIndex, Mv& bestMv, Mv& bestMvd, int& bestMVPIndex, bool& isPerfectMatch)
+{
+  int width = pu.cu->lumaSize().width;
+  int height = pu.cu->lumaSize().height;
+  int xPos = pu.cu->lumaPos().x;
+  int yPos = pu.cu->lumaPos().y;
+
+  unsigned int hashValue1;
+  unsigned int hashValue2;
+  Distortion bestCost = UINT64_MAX;
+
+  if (!TComHash::getBlockHashValue((pu.cs->picture->getOrigBuf()), width, height, xPos, yPos, pu.cu->slice->getSPS()->getBitDepths(), hashValue1, hashValue2))
+  {
+    return false;
+  }
+
+  BlockHash currBlockHash;
+  currBlockHash.x = xPos;
+  currBlockHash.y = yPos;
+  currBlockHash.hashValue2 = hashValue2;
+
+  const int currStride = pu.cs->picture->getOrigBuf().get(COMPONENT_Y).stride;
+  const Pel* pCurr = pu.cs->picture->getOrigBuf().get(COMPONENT_Y).buf + (currBlockHash.y)*currStride + (currBlockHash.x);
+
+  int imvBest = 0;
+
+  int numPredDir = pu.cu->slice->isInterP() ? 1 : 2;
+  for (int refList = 0; refList < numPredDir; refList++)
+  {
+    RefPicList eRefPicList = (refList == 0) ? REF_PIC_LIST_0 : REF_PIC_LIST_1;
+    int refPicNumber = pu.cu->slice->getNumRefIdx(eRefPicList);
+
+    if (pu.cs->slice->getSPS()->getSpsNext().getIBCMode() && eRefPicList == REF_PIC_LIST_0)
+    {
+      refPicNumber--;
+    }
+
+    for (int refIdx = 0; refIdx < refPicNumber; refIdx++)
+    {
+      int bitsOnRefIdx = refIdx + 1;
+      if (refIdx + 1 == refPicNumber)
+      {
+        bitsOnRefIdx--;
+      }
+
+      if (refList == 0 || pu.cu->slice->getList1IdxToList0Idx(refIdx) < 0)
+      {
+        int count = static_cast<int>(pu.cu->slice->getRefPic(eRefPicList, refIdx)->getHashMap()->count(hashValue1));
+        if (count == 0)
+        {
+          continue;
+        }
+
+        list<BlockHash> listBlockHash;
+        selectMatchesInter(pu.cu->slice->getRefPic(eRefPicList, refIdx)->getHashMap()->getFirstIterator(hashValue1), count, listBlockHash, currBlockHash);
+
+        if (listBlockHash.empty())
+        {
+          continue;
+        }
+        AMVPInfo currAMVPInfoPel;
+        AMVPInfo currAMVPInfo4Pel;
+        pu.cu->imv = 2;
+        PU::fillMvpCand(pu, eRefPicList, refIdx, currAMVPInfo4Pel);
+        pu.cu->imv = 1;
+        PU::fillMvpCand(pu, eRefPicList, refIdx, currAMVPInfoPel);
+        CHECK(currAMVPInfoPel.numCand <= 1, "Wrong")
+
+          const Pel* pRefStart = pu.cu->slice->getRefPic(eRefPicList, refIdx)->getRecoBuf().get(COMPONENT_Y).buf;
+        const int refStride = pu.cu->slice->getRefPic(eRefPicList, refIdx)->getRecoBuf().get(COMPONENT_Y).stride;
+
+        m_pcRdCost->selectMotionLambda(pu.cu->transQuantBypass);
+        m_pcRdCost->setCostScale(0);
+
+        list<BlockHash>::iterator it;
+        for (it = listBlockHash.begin(); it != listBlockHash.end(); ++it)
+        {
+          int curMVPIdx = 0;
+          unsigned int curMVPbits = MAX_UINT;
+          Mv cMv((*it).x - currBlockHash.x, (*it).y - currBlockHash.y);
+
+          for (int mvpIdxTemp = 0; mvpIdxTemp < 2; mvpIdxTemp++)
+          {
+            Mv cMvPredPel = currAMVPInfoPel.mvCand[mvpIdxTemp];
+            cMvPredPel >>= 2;
+            m_pcRdCost->setPredictor(cMvPredPel);
+
+            unsigned int tempMVPbits = m_pcRdCost->getBitsOfVectorWithPredictor(cMv.getHor(), cMv.getVer(), 0);
+
+            if (tempMVPbits < curMVPbits)
+            {
+              curMVPbits = tempMVPbits;
+              curMVPIdx = mvpIdxTemp;
+              pu.cu->imv = 1;
+            }
+
+            if ((cMv.getHor() % 4 == 0) && (cMv.getVer() % 4 == 0))
+            {
+              unsigned int bitsMVP4Pel = MAX_UINT;
+              Mv mvPredQuadPel = currAMVPInfo4Pel.mvCand[mvpIdxTemp];
+              mvPredQuadPel >>= 4;
+              m_pcRdCost->setPredictor(mvPredQuadPel);
+              bitsMVP4Pel = m_pcRdCost->getBitsOfVectorWithPredictor(cMv.getHor() >> 2, cMv.getVer() >> 2, 0);
+
+              if (bitsMVP4Pel < curMVPbits)
+              {
+                curMVPbits = bitsMVP4Pel;
+                curMVPIdx = mvpIdxTemp;
+                pu.cu->imv = 2;
+              }
+            }
+
+          }
+
+          const Pel* refPel = pRefStart + (*it).y*refStride + (*it).x;
+          Distortion currSad = getSAD(refPel, refStride, pCurr, currStride, width, height, pu.cu->slice->getSPS()->getBitDepths());
+          curMVPbits += bitsOnRefIdx;
+          Distortion currCost = currSad + m_pcRdCost->getCost(curMVPbits);
+
+          if (!isPerfectMatch)
+          {
+            if (pu.cu->slice->getRefPic(eRefPicList, refIdx)->slices[0]->getSliceQp() <= pu.cu->slice->getSliceQp())
+            {
+              isPerfectMatch = true;
+            }
+          }
+
+          if (currCost < bestCost)
+          {
+            cMv <<= 2;
+            bestCost = currCost;
+            bestRefPicList = eRefPicList;
+            bestRefIndex = refIdx;
+            bestMv = cMv;
+            bestMVPIndex = curMVPIdx;
+            imvBest = pu.cu->imv;
+            if (pu.cu->imv == 2)
+            {
+              bestMvd = cMv - currAMVPInfo4Pel.mvCand[curMVPIdx];
+            }
+            else if (pu.cu->imv == 1)
+            {
+              bestMvd = cMv - currAMVPInfoPel.mvCand[curMVPIdx];
+            }
+          }
+        }
+      }
+    }
+  }
+  pu.cu->imv = imvBest;
+  if (bestMvd == Mv(0, 0))
+  {
+    pu.cu->imv = 0;
+    return false;
+  }
+  return (bestCost < MAX_INT);
+}
+
+bool InterSearch::predInterHashSearch(CodingUnit& cu, Partitioner& partitioner, bool& isPerfectMatch)
+{
+  Mv       bestMv, bestMvd;
+  RefPicList   bestRefPicList;
+  int          bestRefIndex;
+  int          bestMVPIndex;
+
+  auto &pu = *cu.firstPU;
+
+  Mv cMvZero;
+  pu.mv[REF_PIC_LIST_0] = Mv();
+  pu.mv[REF_PIC_LIST_1] = Mv();
+  pu.mvd[REF_PIC_LIST_0] = cMvZero;
+  pu.mvd[REF_PIC_LIST_1] = cMvZero;
+  pu.refIdx[REF_PIC_LIST_0] = NOT_VALID;
+  pu.refIdx[REF_PIC_LIST_1] = NOT_VALID;
+  pu.mvpIdx[REF_PIC_LIST_0] = NOT_VALID;
+  pu.mvpIdx[REF_PIC_LIST_1] = NOT_VALID;
+  pu.mvpNum[REF_PIC_LIST_0] = NOT_VALID;
+  pu.mvpNum[REF_PIC_LIST_1] = NOT_VALID;
+
+  if (xHashInterEstimation(pu, bestRefPicList, bestRefIndex, bestMv, bestMvd, bestMVPIndex, isPerfectMatch))
+  {
+    pu.interDir = static_cast<int>(bestRefPicList) + 1;
+    pu.mv[bestRefPicList] = bestMv;
+    pu.mv[bestRefPicList].hor <<= MV_FRACTIONAL_BITS_DIFF;
+    pu.mv[bestRefPicList].ver <<= MV_FRACTIONAL_BITS_DIFF;
+
+    pu.mvd[bestRefPicList] = bestMvd;
+    pu.refIdx[bestRefPicList] = bestRefIndex;
+    pu.mvpIdx[bestRefPicList] = bestMVPIndex;
+
+    pu.mvpNum[bestRefPicList] = 2;
+
+    PU::spanMotionInfo(pu);
+    PelUnitBuf predBuf = pu.cs->getPredBuf(pu);
+    motionCompensation(pu, predBuf, REF_PIC_LIST_X);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+
+  return true;
+}
+#endif
+
 
 //! search of the best candidate for inter prediction
 void InterSearch::predInterSearch(CodingUnit& cu, Partitioner& partitioner)
@@ -2656,7 +2993,11 @@ void InterSearch::xMotionEstimation(PredictionUnit& pu, PelUnitBuf& origBuf, Ref
   {
     setWpScalingDistParam(iRefIdxPred, eRefPicList, pu.cu->slice);
   }
-
+#if JVET_M0253_HASH_ME
+  m_currRefPicList = eRefPicList;
+  m_currRefPicIndex = iRefIdxPred;
+  m_bSkipFracME = false;
+#endif
   //  Do integer search
   if( ( m_motionEstimationSearchMethod == MESEARCH_FULL ) || bBi || bQTBTMV )
   {
@@ -2939,6 +3280,31 @@ void InterSearch::xTZSearch( const PredictionUnit& pu,
       , cStruct
     );
   }
+#if JVET_M0253_HASH_ME
+  if (m_pcEncCfg->getUseHashME())
+  {
+    int width = pu.cu->lumaSize().width;
+    int height = pu.cu->lumaSize().height;
+    if ((width == height && width <= 64 && width >= 4) || (width == 8 && height == 4) || (width == 4 && height == 8))
+    {
+      Mv otherMvps[5];
+      int numberOfOtherMvps;
+      numberOfOtherMvps = xHashInterPredME(pu, m_currRefPicList, m_currRefPicIndex, otherMvps);
+      for (int i = 0; i < numberOfOtherMvps; i++)
+      {
+        xTZSearchHelp(cStruct, otherMvps[i].getHor(), otherMvps[i].getVer(), 0, 0);
+      }
+      if (numberOfOtherMvps > 0)
+      {
+        // write out best match
+        rcMv.set(cStruct.iBestX, cStruct.iBestY);
+        ruiSAD = cStruct.uiBestSad - m_pcRdCost->getCostOfVectorWithPredictor(cStruct.iBestX, cStruct.iBestY, cStruct.imvShift);
+        m_bSkipFracME = true;
+        return;
+      }
+    }
+  }
+#endif
 
   // start search
   int  iDist = 0;
@@ -3196,6 +3562,33 @@ void InterSearch::xTZSearchSelective( const PredictionUnit& pu,
     );
   }
 
+#if JVET_M0253_HASH_ME
+  if (m_pcEncCfg->getUseHashME())
+  {
+    int width = pu.cu->lumaSize().width;
+    int height = pu.cu->lumaSize().height;
+    if ((width == height && width <= 64 && width >= 4) || (width == 8 && height == 4) || (width == 4 && height == 8))
+    {
+      Mv otherMvps[5];
+      int numberOfOtherMvps;
+      numberOfOtherMvps = xHashInterPredME(pu, m_currRefPicList, m_currRefPicIndex, otherMvps);
+      for (int i = 0; i < numberOfOtherMvps; i++)
+      {
+        xTZSearchHelp(cStruct, otherMvps[i].getHor(), otherMvps[i].getVer(), 0, 0);
+      }
+
+      if (numberOfOtherMvps > 0)
+      {
+        // write out best match
+        rcMv.set(cStruct.iBestX, cStruct.iBestY);
+        ruiSAD = cStruct.uiBestSad - m_pcRdCost->getCostOfVectorWithPredictor(cStruct.iBestX, cStruct.iBestY, cStruct.imvShift);
+        m_bSkipFracME = true;
+        return;
+      }
+    }
+  }
+#endif
+
   // Initial search
   int iBestX = cStruct.iBestX;
   int iBestY = cStruct.iBestY;
@@ -3383,6 +3776,18 @@ void InterSearch::xPatternSearchFracDIF(
   //  Reference pattern initialization (integer scale)
   int         iOffset    = rcMvInt.getHor() + rcMvInt.getVer() * cStruct.iRefStride;
   CPelBuf cPatternRoi(cStruct.piRefY + iOffset, cStruct.iRefStride, *cStruct.pcPatternKey);
+#if JVET_M0253_HASH_ME
+  if (m_bSkipFracME)
+  {
+    Mv baseRefMv(0, 0);
+    rcMvHalf.setZero();
+    m_pcRdCost->setCostScale(0);
+    xExtDIFUpSamplingH(&cPatternRoi);
+    rcMvQter = rcMvInt;   rcMvQter <<= 2;    // for mv-cost
+    ruiCost = xPatternRefinement(cStruct.pcPatternKey, baseRefMv, 1, rcMvQter, !bIsLosslessCoded);
+    return;
+  }
+#endif
 
 
   if (cStruct.imvShift || (pu.cs->sps->getSpsNext().getUseCompositeRef() && cStruct.zeroMV))
@@ -5235,11 +5640,24 @@ void InterSearch::xExtDIFUpSamplingH( CPelBuf* pattern )
   const ChromaFormat chFmt = m_currChromaFormat;
 
   m_if.filterHor(COMPONENT_Y, srcPtr, srcStride, m_filteredBlockTmp[0][0], intStride, width + 1, height + filterSize, 0 << MV_FRACTIONAL_BITS_DIFF, false, chFmt, clpRng);
+#if JVET_M0253_HASH_ME
+  if (!m_bSkipFracME)
+  {
+#endif
   m_if.filterHor(COMPONENT_Y, srcPtr, srcStride, m_filteredBlockTmp[2][0], intStride, width + 1, height + filterSize, 2 << MV_FRACTIONAL_BITS_DIFF, false, chFmt, clpRng);
+#if JVET_M0253_HASH_ME
+  }
+#endif
 
   intPtr = m_filteredBlockTmp[0][0] + halfFilterSize * intStride + 1;
   dstPtr = m_filteredBlock[0][0][0];
   m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width + 0, height + 0, 0 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
+#if JVET_M0253_HASH_ME
+  if (m_bSkipFracME)
+  {
+    return;
+  }
+#endif
 
   intPtr = m_filteredBlockTmp[0][0] + (halfFilterSize - 1) * intStride + 1;
   dstPtr = m_filteredBlock[2][0][0];
diff --git a/source/Lib/EncoderLib/InterSearch.h b/source/Lib/EncoderLib/InterSearch.h
index da9abdf1bea40095271e8b8f1c0eede5da81fddc..51fbd0954f4ef07228ef9de472b4d33979e327c5 100644
--- a/source/Lib/EncoderLib/InterSearch.h
+++ b/source/Lib/EncoderLib/InterSearch.h
@@ -52,6 +52,9 @@
 
 #include "CommonLib/AffineGradientSearch.h"
 #include "CommonLib/IbcHashMap.h"
+#if JVET_M0253_HASH_ME
+#include "CommonLib/Hash.h"
+#endif
 #include <unordered_map>
 #include <vector>
 //! \ingroup EncoderLib
@@ -138,6 +141,12 @@ protected:
   CtxCache*       m_CtxCache;
   DistParam       m_cDistParam;
 
+#if JVET_M0253_HASH_ME
+  RefPicList      m_currRefPicList;
+  int             m_currRefPicIndex;
+  bool            m_bSkipFracME;
+#endif
+
   // Misc.
   Pel            *m_pTempPel;
 
@@ -264,6 +273,14 @@ public:
   void  xIBCEstimation   ( PredictionUnit& pu, PelUnitBuf& origBuf, Mv     *pcMvPred, Mv     &rcMv, Distortion &ruiCost, const int localSearchRangeX, const int localSearchRangeY);
   void  xIBCSearchMVCandUpdate  ( Distortion  uiSad, int x, int y, Distortion* uiSadBestCand, Mv* cMVCand);
   int   xIBCSearchMVChromaRefine( PredictionUnit& pu, int iRoiWidth, int iRoiHeight, int cuPelX, int cuPelY, Distortion* uiSadBestCand, Mv*     cMVCand);
+#if JVET_M0253_HASH_ME
+  void addToSortList(std::list<BlockHash>& listBlockHash, std::list<int>& listCost, int cost, const BlockHash& blockHash);
+  Distortion getSAD(const Pel* refPel, const int refStride, const Pel* curPel, const int curStride, int width, int height, const BitDepths& bitDepths);
+  bool predInterHashSearch(CodingUnit& cu, Partitioner& partitioner, bool& isPerfectMatch);
+  bool xHashInterEstimation(PredictionUnit& pu, RefPicList& bestRefPicList, int& bestRefIndex, Mv& bestMv, Mv& bestMvd, int& bestMVPIndex, bool& isPerfectMatch);
+  int  xHashInterPredME(const PredictionUnit& pu, RefPicList currRefPicList, int currRefPicIndex, Mv bestMv[5]);
+  void selectMatchesInter(const MapIterator& itBegin, int count, std::list<BlockHash>& vecBlockHash, const BlockHash& currBlockHash);
+#endif
 protected:
 
   // -------------------------------------------------------------------------------------------------------------------