diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -172,7 +172,6 @@
 
         if db_field.name in self.raw_id_fields:
             kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
-            kwargs['help_text'] = ''
         elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
             kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
 
diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html
--- a/django/contrib/admin/templates/admin/includes/fieldset.html
+++ b/django/contrib/admin/templates/admin/includes/fieldset.html
@@ -18,8 +18,8 @@
                             {{ field.field }}
                         {% endif %}
                     {% endif %}
-                    {% if field.field.field.help_text %}
-                        <p class="help">{{ field.field.field.help_text|safe }}</p>
+                    {% if field.field.help_text %}
+                        <p class="help">{{ field.field.help_text|safe }}</p>
                     {% endif %}
                 </div>
             {% endfor %}
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -8,7 +8,7 @@
 from django.db.models.query import QuerySet
 from django.db.models.query_utils import QueryWrapper
 from django.utils.encoding import smart_unicode
-from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext
+from django.utils.translation import ugettext_lazy as _, ungettext, ugettext
 from django.utils.functional import curry
 from django.core import exceptions
 from django import forms
@@ -1000,9 +1000,6 @@
 
         Field.__init__(self, **kwargs)
 
-        msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
-        self.help_text = string_concat(self.help_text, ' ', msg)
-
     def get_choices_default(self):
         return Field.get_choices(self, include_blank=False)
 
diff --git a/django/forms/models.py b/django/forms/models.py
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -7,7 +7,7 @@
 from django.utils.encoding import smart_unicode, force_unicode
 from django.utils.datastructures import SortedDict
 from django.utils.text import get_text_list, capfirst
-from django.utils.translation import ugettext_lazy as _, ugettext
+from django.utils.translation import ugettext_lazy as _, ugettext, string_concat
 
 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, \
                                    FieldError
@@ -1027,6 +1027,11 @@
     def __init__(self, queryset, cache_choices=False, required=True,
                  widget=None, label=None, initial=None,
                  help_text=None, *args, **kwargs):
+        msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
+        if help_text is None:
+            help_text = msg
+        else:
+            help_text = string_concat(help_text, ' ', msg)
         super(ModelMultipleChoiceField, self).__init__(queryset, None,
             cache_choices, required, widget, label, initial, help_text,
             *args, **kwargs)
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
--- a/tests/regressiontests/admin_widgets/models.py
+++ b/tests/regressiontests/admin_widgets/models.py
@@ -4,8 +4,8 @@
 from django.core.files.storage import default_storage
 from django.contrib.auth.models import User
 
-class MyFileField(models.FileField): 
-    pass 
+class MyFileField(models.FileField):
+    pass
 
 class Member(models.Model):
     name = models.CharField(max_length=100)
@@ -31,6 +31,27 @@
     def __unicode__(self):
         return self.name
 
+class Director(models.Model):
+    name = models.CharField(max_length=40)
+
+    def __unicode__(self):
+        return self.name
+
+class Genre(models.Model):
+    name = models.CharField(max_length=40)
+
+    def __unicode__(self):
+        return self.name
+
+class Orchestra(models.Model):
+    name = models.CharField(max_length=100)
+    members = models.ManyToManyField(Member, help_text='This is the orchestra members help_text.')
+    director = models.ForeignKey(Director)
+    genres = models.ManyToManyField(Genre, help_text='This is the orchestra genres help_text.')
+
+    def __unicode__(self):
+        return self.name
+
 class HiddenInventoryManager(models.Manager):
     def get_query_set(self):
         return super(HiddenInventoryManager, self).get_query_set().filter(hidden=False)
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
--- a/tests/regressiontests/admin_widgets/tests.py
+++ b/tests/regressiontests/admin_widgets/tests.py
@@ -163,3 +163,33 @@
 
             self.assertContains(response,
                 'Select a valid choice. That choice is not one of the available choices.')
+
+class Ticket14402(DjangoTestCase):
+    fixtures = ["admin-widgets-users.xml"]
+    admin_root = '/widget_admin'
+
+    def setUp(self):
+        self.client.login(username="super", password="secret")
+
+    def tearDown(self):
+        self.client.logout()
+
+    def test_m2m_has_helptext(self):
+        """Non raw_id m2m model field help_text shouldn't be affected by the fix for this ticket."""
+        response = self.client.get('%s/admin_widgets/orchestra/add/' % self.admin_root)
+        self.assert_("This is the orchestra genres help_text." in response.content)
+
+    def test_inline_m2m_has_helptext(self):
+        """Non raw_id m2m model field help_text shouldn't be affected by the fix for this ticket (inline case)."""
+        response = self.client.get('%s/admin_widgets/director/add/' % self.admin_root)
+        self.assert_("This is the orchestra genres help_text." in response.content)
+
+    def test_raw_id_m2m_has_helptext(self):
+        """raw_id m2m model field help_text shouldn't be ignored when displaying its admin widget."""
+        response = self.client.get('%s/admin_widgets/orchestra/add/' % self.admin_root)
+        self.assert_("This is the orchestra members help_text." in response.content)
+
+    def test_raw_id_inline_m2m_has_helptext(self):
+        """raw_id m2m model field help_text shouldn't be ignored when displaying the admin widget (inline case)."""
+        response = self.client.get('%s/admin_widgets/director/add/' % self.admin_root)
+        self.assert_("This is the orchestra members help_text." in response.content)
diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/regressiontests/admin_widgets/widgetadmin.py
--- a/tests/regressiontests/admin_widgets/widgetadmin.py
+++ b/tests/regressiontests/admin_widgets/widgetadmin.py
@@ -22,9 +22,21 @@
 class EventAdmin(admin.ModelAdmin):
     raw_id_fields = ['band']
 
+class OrchestraAdmin(admin.ModelAdmin):
+    raw_id_fields = ['members']
+
+class OrchestraInline(admin.StackedInline):
+    model = models.Orchestra
+    raw_id_fields = ['members']
+
+class DirectorAdmin(admin.ModelAdmin):
+    inlines = [OrchestraInline]
+
 site = WidgetAdmin(name='widget-admin')
 
 site.register(models.User)
 site.register(models.Car, CarAdmin)
 site.register(models.CarTire, CarTireAdmin)
 site.register(models.Event, EventAdmin)
+site.register(models.Orchestra, OrchestraAdmin)
+site.register(models.Director, DirectorAdmin)
